- 投稿日:2020-09-15T21:25:05+09:00
GoでQiitaのトレンドを取得するAPI①叩いてみた
はじめに
前回、GoでQiitaのトレンドを取得するAPI①作ってみた という記事を書きました。
今回はGoでそのAPIを叩いてみたいと思います。
リポジトリはこちら
前回(API)のリポジトリはこちら。
コード
まずは構造体を定義します。
返ってくるjsonと同じ構造になるように定義します。type Qiita struct { Trend `json:"trend"` Scope string `json:"scope"` } type Trend struct { Edges `json:"edges"` } type Edges []struct { FollowingLikers []string `json:"followingLikers"` IsLikeByViewer bool `json:"isLikeByViewer"` IsNewArrival bool `json:"isNewArrival"` HasCodeBlock bool `json:"hasCodeBlock"` Node `json:"node"` } type Node struct { CreatedAt time.Time `json:"createdAt"` LikesCount int `json:"likesCount"` Title string `json:"title"` Uuid string `json:"uuid"` Author `json:"author"` } type Author struct { ProfileImageURL string `json:"profileImageUrl"` UrlName string `json:"urlName"` }次は、実際にGETリクエストを送ります。
返ってきたjsonをデコードして表示させます。var trend Qiita func main() { resp, err := http.Get("http://localhost:8080/trend") if err != nil { log.Fatal() fmt.Fprintln(os.Stderr, "http request err: ", err) } defer resp.Body.Close() bytes, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatal() fmt.Fprintln(os.Stderr, "read err: ", err) } if err := json.Unmarshal(bytes, &trend); err != nil { fmt.Fprintln(os.Stderr, "unmarshal err:", err) } fmt.Println(trend) }実行結果はこんな感じです。
{{[{[] false false true {2020-09-13 08:48:24 +0000 UTC 229 Dockerを体系的に学べる公式チュートリアル和訳 5778e0d9e9c04038903c {https://qiita-image-store.s3.amazonaws.com/0/227791/profile-images/1550453410 Michinosuke}}} {[] false false true {2020-09-13 11:10:37 +0000 UTC 191 kubernetesでもぷよぷよがしたいので同じ色のPodが4個くっついたらdeleteされるcustom controller「くべくべ」を作った c8ff9c824b65ca371cd9 {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38448/profile-images/1600092601 yoitomakenouta}}} {[] false false true {2020-09-12 10:57:35 +0000 UTC 136 スプラトゥーン2のプレイ動画から、やられたシーンだけをディープラーニングで自動抽出する acbc0906046bb7b2b1db {https://avatars3.githubusercontent.com/u/16898831?v=4 tfandkusu}}} {[] false false true {2020-09-12 13:59:38 +0000 UTC 111 Kubernetesの負荷試験で絶対に担保したい13のチェックリスト 56a5442c1fc4d714c941 {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45875/profile-images/1595854294 taisho6339}}} {[] false false true {2020-09-12 03:08:08 +0000 UTC 148 間違えて違うブランチで作業を始めてしまったときの対処法【Git】 dc21de47c617b0d0eebf {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675596/profile-images/1598685340 paleo_engineer}}} {[] false false false {2020-09-12 00:49:28 +0000 UTC 64 【翻訳】wordpressのプラグインで毎月5万収益を得ている話 ab1b2b6d5676dc55144b {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705937/profile-images/1599573315 kirishima_app}}} {[] false false true {2020-09-13 05:42:09 +0000 UTC 48 論文と公共データベースを使って無料で始めるAI創薬 a12f7bf594a56a2b22ba {https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/329816/ba6cbc8b71c92411f763dc9334212db0a2d0f08f/x_large.png?1589641830 kimisyo}}} {[] false false true {2020-09-13 04:57:19 +0000 UTC 37 FlutterにFirebase(Firestore)を導入したら、iOSのビルドが遅くなった 290d61a1731e50201a61 {https://avatars1.githubusercontent.com/u/38914807?v=4 sakaihiroaki}}} {[] false false true {2020-09-13 18:56:58 +0000 UTC 39 モナリザの写真を自分の顔とシンクロさせて動かしてみる、流行りの深層学習モデルFOMMについて解説&実装 e0ce1701e25e315713c4 {https://qiita-image-store.s3.amazonaws.com/0/56557/profile-images/1473694040 tommy19970714}}} {[] false true false {2020-09-14 07:59:56 +0000 UTC 25 VRChat向けマッチングサービス「sucrALrose(スクラロース)」でサイバー攻撃。何が起こった? 6136be8b48080554fead {https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/535270/3e59522dfca791fd0ce7f98d863a9a796ca95a16/large.png?1574159780 kkent030315}}} {[] false false false {2020-09-13 01:57:50 +0000 UTC 37 音声分析におけるフーリエ変換とスペクトログラムを理解する 6af2cc4c4be0c57bef06 {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651198/profile-images/1591512068 shirowanisan}}} {[] false false true {2020-09-12 23:07:03 +0000 UTC 25 C++上でPythonのコードを実行する(Boost.Pythonの利用) 1e2c24e80212a4c4c584 {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192805/profile-images/1556565825 m4saka}}} {[] false false true {2020-09-13 10:34:26 +0000 UTC 23 React Live Conference 2020 に参加した話 f1c2e9c6fde046aed05c {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103475/profile-images/1591193480 nitaking}}} {[] false false true {2020-09-12 13:32:05 +0000 UTC 29 SwiftUIとCombineの練習がてらmacOS向けのピアノアプリ作ってみた 0230a14ec6c3a8174568 {https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/67153/e88c7b2790238820700627c60482ade9228609f7/x_large.png?1575435001 Kyome}}} {[] false false false {2020-09-13 09:55:28 +0000 UTC 18 micro:bitプログラミング入門講座テキスト df4b502b43fde7eba35b {https://qiita-image-store.s3.amazonaws.com/0/26536/profile-images/1473684616 noanoa07}}} {[] false false false {2020-09-13 17:21:00 +0000 UTC 13 ThreeDPoseTracker Ver.0.3.0アップデート 82b1e50de8d81e554721 {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/374813/profile-images/1598234502 yukihiko_a}}} {[] false false true {2020-09-12 11:29:02 +0000 UTC 26 production-ready なGo製2Dゲームライブラリ Ebiten の紹介 & リンク集 1fd6077327f99245b807 {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/348791/profile-images/1565355993 eihigh}}} {[] false true false {2020-09-13 23:56:21 +0000 UTC 18 【翻訳】自動投稿ツールが300万で売れた話(契約内容も) 58d77a7b91e013f22429 {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705937/profile-images/1599573315 kirishima_app}}} {[] false false true {2020-09-11 23:36:29 +0000 UTC 25 Docker × Laravel 8 Jetstream でログイン、ユーザー登録、2要素認証を実装する 7824d1293fef4698c212 {https://qiita-image-store.s3.amazonaws.com/0/52879/profile-images/1540380605 ucan-lab}}} {[] false false true {2020-09-12 10:57:47 +0000 UTC 44 データ構造 Fenwick tree (binary indexed tree, BIT) にどっぷりと入門し、その美しき構造に心を洗われたい方のための紹介記事です! 7d50ff180a4e5c294cb7 {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352395/profile-images/1557632578 ngtkana}}} {[] false false true {2020-09-12 09:04:26 +0000 UTC 30 Ractor超入門 f9d1917fda770bcdbe2a {https://qiita-image-store.s3.amazonaws.com/0/191521/profile-images/1531325180 S_H_}}} {[] false false true {2020-09-13 15:03:42 +0000 UTC 12 Docker環境でRails停止時にExit code 1が発生する b18eb09d016a6bf33568 {https://qiita-image-store.s3.amazonaws.com/0/27454/profile-images/1473685014 naomichi-y}}} {[] false false true {2020-09-12 08:48:31 +0000 UTC 17 Prism の Prism Full App (.NET Core) テンプレートを見てみよう cfbf5c9eaea6c5aed4e1 {https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/70288/edf16d4d4fdd1abcb3e61b06730b0609cdc77adb/x_large.png?1585128349 okazuki}}} {[] false true true {2020-09-14 12:41:15 +0000 UTC 13 TypeScriptの型で遊ぶ時、再帰制限を(合法的に)突破する 44c1b012d66aae1dc2c0 {https://qiita-image-store.s3.amazonaws.com/0/71477/profile-images/1487741973 kazatsuyu}}} {[] false false true {2020-09-12 01:56:00 +0000 UTC 18 PythonでDICOM画像を扱う f145675055796c69e228 {https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/680528/4c5642d036920f85daf6640335027d9a0387cecc/x_large.png?1595678226 asparagasu}}} {[] false false true {2020-09-12 08:11:54 +0000 UTC 27 自分のQiita記事をインクリメンタル検索するAlfred Workflowを作ってみた 6909b534a59d3550093e {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/profile-images/1596162182 ryo2132}}} {[] false true true {2020-09-14 00:14:21 +0000 UTC 4 ISUCON10 予選でやったこと、失敗したこと 40f77453f8c91d49014e {https://avatars1.githubusercontent.com/u/5134941?v=4 lostfind}}} {[] false true true {2020-09-14 08:34:10 +0000 UTC 4 WebCodecs + WebRTC Insertable Streams で Scalable live w/ P2P を実現してみた 177401804c69bd6cfd64 {https://qiita-image-store.s3.amazonaws.com/0/83466/profile-images/1473702785 komasshu}}} {[] false true true {2020-09-13 20:37:40 +0000 UTC 6 Azure Cosmos DB の無償枠 (Free Tier) の注意点 ffc3a44eef75bda5a502 {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/348116/profile-images/1580139929 ymasaoka}}} {[] false true true {2020-09-14 06:51:39 +0000 UTC 7 Unity 開発に関する 50 の Tips 〜ベストプラクティス〜(2016 Edition) e48a011074bc65d57d1a {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55937/profile-images/1592202863 sh1ch}}}]} daily}見にくい!!
欲しい値だけを取得してみます。
今回は、記事タイトルと記事URLを表示させたいと思います。
URLはユーザー名/ + items/ + Uuid で表示されるので、AuthorのUrlNameとNodeのUuidを取得してみます。package main import ( "encoding/json" "fmt" "io/ioutil" "log" "net/http" "os" ) type Qiita struct { Trend `json:"trend"` } type Trend struct { Edges `json:"edges"` } type Edges []struct { Node `json:"node"` } type Node struct { Title string `json:"title"` Uuid string `json:"uuid"` Author `json:"author"` } type Author struct { UrlName string `json:"urlName"` } var trend Qiita func main() { resp, err := http.Get("http://localhost:8080/trend") if err != nil { log.Fatal() fmt.Fprintln(os.Stderr, "http request err: ", err) } defer resp.Body.Close() bytes, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatal() fmt.Fprintln(os.Stderr, "read err: ", err) } if err := json.Unmarshal(bytes, &trend); err != nil { fmt.Fprintln(os.Stderr, "unmarshal err:", err) } for i, v := range trend.Trend.Edges { fmt.Println("No.", i+1) fmt.Printf("\t%s\n\t%s\n", v.Node.Title, "https://qiita.com/"+v.Node.Author.UrlName+"/items/"+v.Node.Uuid) } }取得しない値については構造体を定義しなくても大丈夫です。
実行するとこんな感じ。
No. 1 Dockerを体系的に学べる公式チュートリアル和訳 https://qiita.com/Michinosuke/items/5778e0d9e9c04038903c No. 2 kubernetesでもぷよぷよがしたいので同じ色のPodが4個くっついたらdeleteされるcustom controller「くべくべ」を作った https://qiita.com/yoitomakenouta/items/c8ff9c824b65ca371cd9 No. 3 スプラトゥーン2のプレイ動画から、やられたシーンだけをディープラーニングで自動抽出する https://qiita.com/tfandkusu/items/acbc0906046bb7b2b1db No. 4 モナリザの写真を自分の顔とシンクロさせて動かしてみる、流行りの深層学習モデルFOMMについて解説&実装 https://qiita.com/tommy19970714/items/e0ce1701e25e315713c4 No. 5 Kubernetesの負荷試験で絶対に担保したい13のチェックリスト https://qiita.com/taisho6339/items/56a5442c1fc4d714c941 No. 6 【翻訳】自動投稿ツールが300万で売れた話(契約内容も) https://qiita.com/kirishima_app/items/58d77a7b91e013f22429 No. 7 論文と公共データベースを使って無料で始めるAI創薬 https://qiita.com/kimisyo/items/a12f7bf594a56a2b22ba No. 8 VRChat向けマッチングサービス「sucrALrose(スクラロース)」でサイバー攻撃。何が起こった? https://qiita.com/kkent030315/items/6136be8b48080554fead No. 9 TypeScriptの型で遊ぶ時、再帰制限を(合法的に)突破する https://qiita.com/kazatsuyu/items/44c1b012d66aae1dc2c0 No. 10 FlutterにFirebase(Firestore)を導入したら、iOSのビルドが遅くなった https://qiita.com/sakaihiroaki/items/290d61a1731e50201a61 No. 11 React Live Conference 2020 に参加した話 https://qiita.com/nitaking/items/f1c2e9c6fde046aed05c No. 12 SwiftUIとCombineの練習がてらmacOS向けのピアノアプリ作ってみた https://qiita.com/Kyome/items/0230a14ec6c3a8174568 No. 13 Unity 開発に関する 50 の Tips 〜ベストプラクティス〜(2016 Edition) https://qiita.com/sh1ch/items/e48a011074bc65d57d1a No. 14 【R】データ要約ガチ勢のためのgtsummaryで表を書こう https://qiita.com/yanami/items/117851de49024f5980d0 No. 15 音声分析におけるフーリエ変換とスペクトログラムを理解する https://qiita.com/shirowanisan/items/6af2cc4c4be0c57bef06 No. 16 C++上でPythonのコードを実行する(Boost.Pythonの利用) https://qiita.com/m4saka/items/1e2c24e80212a4c4c584 No. 17 ThreeDPoseTracker Ver.0.3.0アップデート https://qiita.com/yukihiko_a/items/82b1e50de8d81e554721 No. 18 JAWS SONIC 2020 & MIDNIGHT JAWS 2020 登壇資料まとめ https://qiita.com/tosiooooooo/items/6b2b9ba3fd7e7ad68604 No. 19 「重複エラー」を伝える英語フレーズ https://qiita.com/suin/items/b51f1434723f4979e8c8 No. 20 Docker環境でRails停止時にExit code 1が発生する https://qiita.com/naomichi-y/items/b18eb09d016a6bf33568 No. 21 Azure Cosmos DB の無償枠 (Free Tier) の注意点 https://qiita.com/ymasaoka/items/ffc3a44eef75bda5a502 No. 22 画面をデザインするということ https://qiita.com/megumu-u/items/73b728ad1d381717d731 No. 23 Linuxカーネル、その29年の歴史レポート https://qiita.com/rana_kualu/items/27c883922061c33af0a4 No. 24 VSCodeをIDE化する方法 https://qiita.com/Midoliy/items/d19dc8c1c97078b1e640 No. 25 production-ready なGo製2Dゲームライブラリ Ebiten の紹介 & リンク集 https://qiita.com/eihigh/items/1fd6077327f99245b807 No. 26 WebCodecs + WebRTC Insertable Streams で Scalable live w/ P2P を実現してみた https://qiita.com/komasshu/items/177401804c69bd6cfd64 No. 27 データ構造 Fenwick tree (binary indexed tree, BIT) にどっぷりと入門し、その美しき構造に心を洗われたい方のための紹介記事です! https://qiita.com/ngtkana/items/7d50ff180a4e5c294cb7 No. 28 Ractor超入門 https://qiita.com/S_H_/items/f9d1917fda770bcdbe2a No. 29 ISUCON10 予選でやったこと、失敗したこと https://qiita.com/lostfind/items/40f77453f8c91d49014e No. 30 pythonで極力簡単にGUIを作る【tkinter編】 https://qiita.com/Ringa_hyj/items/a580f2d60eadcf4dbb2fちゃんと欲しい値だけ取得できました!
さいごに
qiitaのトレンド記事を取得したい方は是非使ってみてください!
- 投稿日:2020-09-15T21:25:05+09:00
GoでQiitaのトレンドを取得するAPI②叩いてみた
はじめに
前回、GoでQiitaのトレンドを取得するAPI①作ってみた という記事を書きました。
今回はGoでそのAPIを叩いてみたいと思います。
コード
まずは構造体を定義します。
返ってくるjsonと同じ構造になるように定義します。type Qiita struct { Trend `json:"trend"` Scope string `json:"scope"` } type Trend struct { Edges `json:"edges"` } type Edges []struct { FollowingLikers []string `json:"followingLikers"` IsLikeByViewer bool `json:"isLikeByViewer"` IsNewArrival bool `json:"isNewArrival"` HasCodeBlock bool `json:"hasCodeBlock"` Node `json:"node"` } type Node struct { CreatedAt time.Time `json:"createdAt"` LikesCount int `json:"likesCount"` Title string `json:"title"` Uuid string `json:"uuid"` Author `json:"author"` } type Author struct { ProfileImageURL string `json:"profileImageUrl"` UrlName string `json:"urlName"` }次は、実際にGETリクエストを送ります。
返ってきたjsonをデコードして表示させます。var trend Qiita func main() { resp, err := http.Get("http://localhost:8080/trend") if err != nil { log.Fatal() fmt.Fprintln(os.Stderr, "http request err: ", err) } defer resp.Body.Close() bytes, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatal() fmt.Fprintln(os.Stderr, "read err: ", err) } if err := json.Unmarshal(bytes, &trend); err != nil { fmt.Fprintln(os.Stderr, "unmarshal err:", err) } fmt.Println(trend) }実行結果はこんな感じです。
{{[{[] false false true {2020-09-13 08:48:24 +0000 UTC 229 Dockerを体系的に学べる公式チュートリアル和訳 5778e0d9e9c04038903c {https://qiita-image-store.s3.amazonaws.com/0/227791/profile-images/1550453410 Michinosuke}}} {[] false false true {2020-09-13 11:10:37 +0000 UTC 191 kubernetesでもぷよぷよがしたいので同じ色のPodが4個くっついたらdeleteされるcustom controller「くべくべ」を作った c8ff9c824b65ca371cd9 {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/38448/profile-images/1600092601 yoitomakenouta}}} {[] false false true {2020-09-12 10:57:35 +0000 UTC 136 スプラトゥーン2のプレイ動画から、やられたシーンだけをディープラーニングで自動抽出する acbc0906046bb7b2b1db {https://avatars3.githubusercontent.com/u/16898831?v=4 tfandkusu}}} {[] false false true {2020-09-12 13:59:38 +0000 UTC 111 Kubernetesの負荷試験で絶対に担保したい13のチェックリスト 56a5442c1fc4d714c941 {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/45875/profile-images/1595854294 taisho6339}}} {[] false false true {2020-09-12 03:08:08 +0000 UTC 148 間違えて違うブランチで作業を始めてしまったときの対処法【Git】 dc21de47c617b0d0eebf {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/675596/profile-images/1598685340 paleo_engineer}}} {[] false false false {2020-09-12 00:49:28 +0000 UTC 64 【翻訳】wordpressのプラグインで毎月5万収益を得ている話 ab1b2b6d5676dc55144b {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705937/profile-images/1599573315 kirishima_app}}} {[] false false true {2020-09-13 05:42:09 +0000 UTC 48 論文と公共データベースを使って無料で始めるAI創薬 a12f7bf594a56a2b22ba {https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/329816/ba6cbc8b71c92411f763dc9334212db0a2d0f08f/x_large.png?1589641830 kimisyo}}} {[] false false true {2020-09-13 04:57:19 +0000 UTC 37 FlutterにFirebase(Firestore)を導入したら、iOSのビルドが遅くなった 290d61a1731e50201a61 {https://avatars1.githubusercontent.com/u/38914807?v=4 sakaihiroaki}}} {[] false false true {2020-09-13 18:56:58 +0000 UTC 39 モナリザの写真を自分の顔とシンクロさせて動かしてみる、流行りの深層学習モデルFOMMについて解説&実装 e0ce1701e25e315713c4 {https://qiita-image-store.s3.amazonaws.com/0/56557/profile-images/1473694040 tommy19970714}}} {[] false true false {2020-09-14 07:59:56 +0000 UTC 25 VRChat向けマッチングサービス「sucrALrose(スクラロース)」でサイバー攻撃。何が起こった? 6136be8b48080554fead {https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/535270/3e59522dfca791fd0ce7f98d863a9a796ca95a16/large.png?1574159780 kkent030315}}} {[] false false false {2020-09-13 01:57:50 +0000 UTC 37 音声分析におけるフーリエ変換とスペクトログラムを理解する 6af2cc4c4be0c57bef06 {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/651198/profile-images/1591512068 shirowanisan}}} {[] false false true {2020-09-12 23:07:03 +0000 UTC 25 C++上でPythonのコードを実行する(Boost.Pythonの利用) 1e2c24e80212a4c4c584 {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/192805/profile-images/1556565825 m4saka}}} {[] false false true {2020-09-13 10:34:26 +0000 UTC 23 React Live Conference 2020 に参加した話 f1c2e9c6fde046aed05c {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/103475/profile-images/1591193480 nitaking}}} {[] false false true {2020-09-12 13:32:05 +0000 UTC 29 SwiftUIとCombineの練習がてらmacOS向けのピアノアプリ作ってみた 0230a14ec6c3a8174568 {https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/67153/e88c7b2790238820700627c60482ade9228609f7/x_large.png?1575435001 Kyome}}} {[] false false false {2020-09-13 09:55:28 +0000 UTC 18 micro:bitプログラミング入門講座テキスト df4b502b43fde7eba35b {https://qiita-image-store.s3.amazonaws.com/0/26536/profile-images/1473684616 noanoa07}}} {[] false false false {2020-09-13 17:21:00 +0000 UTC 13 ThreeDPoseTracker Ver.0.3.0アップデート 82b1e50de8d81e554721 {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/374813/profile-images/1598234502 yukihiko_a}}} {[] false false true {2020-09-12 11:29:02 +0000 UTC 26 production-ready なGo製2Dゲームライブラリ Ebiten の紹介 & リンク集 1fd6077327f99245b807 {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/348791/profile-images/1565355993 eihigh}}} {[] false true false {2020-09-13 23:56:21 +0000 UTC 18 【翻訳】自動投稿ツールが300万で売れた話(契約内容も) 58d77a7b91e013f22429 {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/705937/profile-images/1599573315 kirishima_app}}} {[] false false true {2020-09-11 23:36:29 +0000 UTC 25 Docker × Laravel 8 Jetstream でログイン、ユーザー登録、2要素認証を実装する 7824d1293fef4698c212 {https://qiita-image-store.s3.amazonaws.com/0/52879/profile-images/1540380605 ucan-lab}}} {[] false false true {2020-09-12 10:57:47 +0000 UTC 44 データ構造 Fenwick tree (binary indexed tree, BIT) にどっぷりと入門し、その美しき構造に心を洗われたい方のための紹介記事です! 7d50ff180a4e5c294cb7 {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/352395/profile-images/1557632578 ngtkana}}} {[] false false true {2020-09-12 09:04:26 +0000 UTC 30 Ractor超入門 f9d1917fda770bcdbe2a {https://qiita-image-store.s3.amazonaws.com/0/191521/profile-images/1531325180 S_H_}}} {[] false false true {2020-09-13 15:03:42 +0000 UTC 12 Docker環境でRails停止時にExit code 1が発生する b18eb09d016a6bf33568 {https://qiita-image-store.s3.amazonaws.com/0/27454/profile-images/1473685014 naomichi-y}}} {[] false false true {2020-09-12 08:48:31 +0000 UTC 17 Prism の Prism Full App (.NET Core) テンプレートを見てみよう cfbf5c9eaea6c5aed4e1 {https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/70288/edf16d4d4fdd1abcb3e61b06730b0609cdc77adb/x_large.png?1585128349 okazuki}}} {[] false true true {2020-09-14 12:41:15 +0000 UTC 13 TypeScriptの型で遊ぶ時、再帰制限を(合法的に)突破する 44c1b012d66aae1dc2c0 {https://qiita-image-store.s3.amazonaws.com/0/71477/profile-images/1487741973 kazatsuyu}}} {[] false false true {2020-09-12 01:56:00 +0000 UTC 18 PythonでDICOM画像を扱う f145675055796c69e228 {https://s3-ap-northeast-1.amazonaws.com/qiita-image-store/0/680528/4c5642d036920f85daf6640335027d9a0387cecc/x_large.png?1595678226 asparagasu}}} {[] false false true {2020-09-12 08:11:54 +0000 UTC 27 自分のQiita記事をインクリメンタル検索するAlfred Workflowを作ってみた 6909b534a59d3550093e {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/114396/profile-images/1596162182 ryo2132}}} {[] false true true {2020-09-14 00:14:21 +0000 UTC 4 ISUCON10 予選でやったこと、失敗したこと 40f77453f8c91d49014e {https://avatars1.githubusercontent.com/u/5134941?v=4 lostfind}}} {[] false true true {2020-09-14 08:34:10 +0000 UTC 4 WebCodecs + WebRTC Insertable Streams で Scalable live w/ P2P を実現してみた 177401804c69bd6cfd64 {https://qiita-image-store.s3.amazonaws.com/0/83466/profile-images/1473702785 komasshu}}} {[] false true true {2020-09-13 20:37:40 +0000 UTC 6 Azure Cosmos DB の無償枠 (Free Tier) の注意点 ffc3a44eef75bda5a502 {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/348116/profile-images/1580139929 ymasaoka}}} {[] false true true {2020-09-14 06:51:39 +0000 UTC 7 Unity 開発に関する 50 の Tips 〜ベストプラクティス〜(2016 Edition) e48a011074bc65d57d1a {https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55937/profile-images/1592202863 sh1ch}}}]} daily}見にくい!!
欲しい値だけを取得してみます。
今回は、記事タイトルと記事URLを表示させたいと思います。
URLはユーザー名/ + items/ + Uuid で表示されるので、AuthorのUrlNameとNodeのUuidを取得してみます。package main import ( "encoding/json" "fmt" "io/ioutil" "log" "net/http" "os" ) type Qiita struct { Trend `json:"trend"` } type Trend struct { Edges `json:"edges"` } type Edges []struct { Node `json:"node"` } type Node struct { Title string `json:"title"` Uuid string `json:"uuid"` Author `json:"author"` } type Author struct { UrlName string `json:"urlName"` } var trend Qiita func main() { resp, err := http.Get("http://localhost:8080/trend") if err != nil { log.Fatal() fmt.Fprintln(os.Stderr, "http request err: ", err) } defer resp.Body.Close() bytes, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatal() fmt.Fprintln(os.Stderr, "read err: ", err) } if err := json.Unmarshal(bytes, &trend); err != nil { fmt.Fprintln(os.Stderr, "unmarshal err:", err) } for i, v := range trend.Trend.Edges { fmt.Println("No.", i+1) fmt.Printf("\t%s\n\t%s\n", v.Node.Title, "https://qiita.com/"+v.Node.Author.UrlName+"/items/"+v.Node.Uuid) } }取得しない値については構造体を定義しなくても大丈夫です。
実行するとこんな感じ。
No. 1 Dockerを体系的に学べる公式チュートリアル和訳 https://qiita.com/Michinosuke/items/5778e0d9e9c04038903c No. 2 kubernetesでもぷよぷよがしたいので同じ色のPodが4個くっついたらdeleteされるcustom controller「くべくべ」を作った https://qiita.com/yoitomakenouta/items/c8ff9c824b65ca371cd9 No. 3 スプラトゥーン2のプレイ動画から、やられたシーンだけをディープラーニングで自動抽出する https://qiita.com/tfandkusu/items/acbc0906046bb7b2b1db No. 4 モナリザの写真を自分の顔とシンクロさせて動かしてみる、流行りの深層学習モデルFOMMについて解説&実装 https://qiita.com/tommy19970714/items/e0ce1701e25e315713c4 No. 5 Kubernetesの負荷試験で絶対に担保したい13のチェックリスト https://qiita.com/taisho6339/items/56a5442c1fc4d714c941 No. 6 【翻訳】自動投稿ツールが300万で売れた話(契約内容も) https://qiita.com/kirishima_app/items/58d77a7b91e013f22429 No. 7 論文と公共データベースを使って無料で始めるAI創薬 https://qiita.com/kimisyo/items/a12f7bf594a56a2b22ba No. 8 VRChat向けマッチングサービス「sucrALrose(スクラロース)」でサイバー攻撃。何が起こった? https://qiita.com/kkent030315/items/6136be8b48080554fead No. 9 TypeScriptの型で遊ぶ時、再帰制限を(合法的に)突破する https://qiita.com/kazatsuyu/items/44c1b012d66aae1dc2c0 No. 10 FlutterにFirebase(Firestore)を導入したら、iOSのビルドが遅くなった https://qiita.com/sakaihiroaki/items/290d61a1731e50201a61 No. 11 React Live Conference 2020 に参加した話 https://qiita.com/nitaking/items/f1c2e9c6fde046aed05c No. 12 SwiftUIとCombineの練習がてらmacOS向けのピアノアプリ作ってみた https://qiita.com/Kyome/items/0230a14ec6c3a8174568 No. 13 Unity 開発に関する 50 の Tips 〜ベストプラクティス〜(2016 Edition) https://qiita.com/sh1ch/items/e48a011074bc65d57d1a No. 14 【R】データ要約ガチ勢のためのgtsummaryで表を書こう https://qiita.com/yanami/items/117851de49024f5980d0 No. 15 音声分析におけるフーリエ変換とスペクトログラムを理解する https://qiita.com/shirowanisan/items/6af2cc4c4be0c57bef06 No. 16 C++上でPythonのコードを実行する(Boost.Pythonの利用) https://qiita.com/m4saka/items/1e2c24e80212a4c4c584 No. 17 ThreeDPoseTracker Ver.0.3.0アップデート https://qiita.com/yukihiko_a/items/82b1e50de8d81e554721 No. 18 JAWS SONIC 2020 & MIDNIGHT JAWS 2020 登壇資料まとめ https://qiita.com/tosiooooooo/items/6b2b9ba3fd7e7ad68604 No. 19 「重複エラー」を伝える英語フレーズ https://qiita.com/suin/items/b51f1434723f4979e8c8 No. 20 Docker環境でRails停止時にExit code 1が発生する https://qiita.com/naomichi-y/items/b18eb09d016a6bf33568 No. 21 Azure Cosmos DB の無償枠 (Free Tier) の注意点 https://qiita.com/ymasaoka/items/ffc3a44eef75bda5a502 No. 22 画面をデザインするということ https://qiita.com/megumu-u/items/73b728ad1d381717d731 No. 23 Linuxカーネル、その29年の歴史レポート https://qiita.com/rana_kualu/items/27c883922061c33af0a4 No. 24 VSCodeをIDE化する方法 https://qiita.com/Midoliy/items/d19dc8c1c97078b1e640 No. 25 production-ready なGo製2Dゲームライブラリ Ebiten の紹介 & リンク集 https://qiita.com/eihigh/items/1fd6077327f99245b807 No. 26 WebCodecs + WebRTC Insertable Streams で Scalable live w/ P2P を実現してみた https://qiita.com/komasshu/items/177401804c69bd6cfd64 No. 27 データ構造 Fenwick tree (binary indexed tree, BIT) にどっぷりと入門し、その美しき構造に心を洗われたい方のための紹介記事です! https://qiita.com/ngtkana/items/7d50ff180a4e5c294cb7 No. 28 Ractor超入門 https://qiita.com/S_H_/items/f9d1917fda770bcdbe2a No. 29 ISUCON10 予選でやったこと、失敗したこと https://qiita.com/lostfind/items/40f77453f8c91d49014e No. 30 pythonで極力簡単にGUIを作る【tkinter編】 https://qiita.com/Ringa_hyj/items/a580f2d60eadcf4dbb2fちゃんと欲しい値だけ取得できました!
さいごに
qiitaのトレンド記事を取得したい方は是非使ってみてください!
- 投稿日:2020-09-15T19:30:19+09:00
Go言語でTODOアプリwithクリーンアーキテクチャ
この記事ではクリーンアーキテクチャを学びながらGoで簡単なTODOアプリを作成する記事です。勉強のまとめをした物なので間違っている点があればご指摘をお願いします。
クリーンアーキテクチャ
クリーンアーキテクチャとは
ソフトウェアをレイターごとに役割を切り分けることにより関心を分離させることができるという考え方です。
翻訳版はこちら採用する目的
フレームワーク独立
フレームワークを道具として使うことでアプリケーションがフレームワークに依存せず、制約を受けないテスト可能
レイヤー同士が独立しているのでテストがしやすい。UI独立
ビジネスルールを変えずにUIを変更できる。データベース独立
postgresやMySqlなどのデータベースの変更が容易である。外部機能独立
ビジネスルールは外部レイヤーに依存しない。レイヤー
説明するのは4つだが4つでなくても良いです。
- Entities
ビジネスルールのためのデータ構造や関数
- Use Case
アプリケーション固有のビジネスルールを定義する。エンティティとのデータをやり取りする。
- Interfce Adapters
アダプターの集合。内側にある層から外側の層にデータを変換して受け渡す。逆に外側から内側にデータを受け渡す。
- Frameworks & Drivers
フレームワークやツールから構成される。
レイヤー間の規則
依存ルール。各レイヤーは内側のレイヤーのことしか知らず、外側のレイヤーについては知らない。
内側の円は、外側の円についてなにも知ることはない。とくに、外側の円で宣言されたものの名前を、内側の円から言及してはならない。これは、関数、クラス、変数、あるいはその他、名前が付けられたソフトウェアのエンティティすべてに言える。
同様に、外側の円で使われているデータフォーマットを内側の円で使うべきではない。とくに、それらのフォーマットが、外側の円でフレームワークによって生成されているのであれば。外側の円のどんなものも、内側の円に影響を与えるべきではないのだ。実装するアプリケーション
作成したアプリケーションのコードはこちらです。
TODOとユーザーを使った単純なアプリケーションです。WebAPIを提供します。
ユーザーのCRUD, TODOのCRUDを行います。開発環境
Docker 19.03.12
docker-compose 1.26.2
Go 1.13
PostgreSQL
github.com/lib/pq 1.8.0ディレクトリ構成
├── domain │ └── model │ ├── todo.go │ └── user.go ├── infrastructure │ ├── router.go │ ├── sqlhandler.go │ └── sqlhandler_test.go ├── interfaces │ ├── controllers │ │ ├── todoController.go │ │ └── userController.go │ └── database │ ├── sqlhandler.go │ ├── todoRepository.go │ └── userRepository.go ├── main.go └── usecase ├── todoInteractor.go ├── todoRepository.go ├── userInteractor.go └── userRepository.goとします。アーキテクチャ図とディレクト構成の関係は次のようになっています。
依存の方向 レイヤー ディレクトリ名(パッケージ名) - Entities domain(model) ↑ Use cases usecase(usecase) ↑ interfaces interfaces(controllers, database) ↑ Frameworks & Drivers infrastracture(infrastructure) データの流れ
DB操作
Use case(Interactor)
→interface(Repository)
→infrastructure(Sqlhandler)
リクエスト処理
アクセス→infrastructure(handler)
→interfaces(controller)
→httpリスポンスEntitiesレイヤ
User
,Todo
はビジネスルールのためのデータ構造なのでここで定義します。また、今回は追加していないユーザーの名前の文字数制限などはここで定義する必要があります。User
とTodo
は次のようになります。/domain/model/user.gopackage model type User struct { ID int `json:"id"` FirstName string `json:"first_name"` LastName string `json:"last_name"` } type Users []User/domain/model/todo.gopackage model import "time" type Todo struct { ID int `json:"id"` Title string `json:"title"` Note string `json:"note"` DueDate time.Time `json:"due_date"` UserID int `json:"user_id"` } type Todos []Todoinfrastructureレイヤ
このレイヤでは外部のツールであるPostgresとの接続を行います。
/infrastructure/sqlhandler.gopackage infrastructure import ( "database/sql" "fmt" "github.com/kikils/golang-todo/interfaces/database" _ "github.com/lib/pq" // postres driver ) type Sqlhandler struct { DB *sql.DB } func NewSqlhandler() *Sqlhandler { connStr := "postgres://postgres:postgres@postgres:5432/postgres?sslmode=disable" db, err := sql.Open("postgres", connStr) if err != nil { return nil } err = db.Ping() if err != nil { fmt.Println("here") return nil } return &Sqlhandler{db} } func (handler *Sqlhandler) Execute(statement string, args ...interface{}) (database.Result, error) { res := SqlResult{} result, err := handler.DB.Exec(statement, args...) if err != nil { return res, err } res.Result = result return res, nil } func (handler *Sqlhandler) Query(statement string, args ...interface{}) (database.Row, error) { rows, err := handler.DB.Query(statement, args...) if err != nil { return new(SqlRow), err } row := new(SqlRow) row.Rows = rows return row, nil } type SqlResult struct { Result sql.Result } func (r SqlResult) LastInsertId() (int64, error) { return r.Result.LastInsertId() } func (r SqlResult) RowsAffected() (int64, error) { return r.Result.RowsAffected() } type SqlRow struct { Rows *sql.Rows } func (r SqlRow) Scan(dest ...interface{}) error { return r.Rows.Scan(dest...) } func (r SqlRow) Next() bool { return r.Rows.Next() } func (r SqlRow) Close() error { return r.Rows.Close() }次に実際にSQlとのデータのやり取りを行う処理を
interfaces/database
に書きます。interfaces/databaseレイヤ
このレイヤには
UserRepository
とTodoRepository
を宣言します。
例としてuserRepository.go
を示します。SQL
を叩いていることがわかります。/interfaces/database/userRepository.gopackage database import ( "github.com/kikils/golang-todo/domain/model" ) type UserRepository struct { Sqlhandler } func (repo *UserRepository) Store(u model.User) (id int, err error) { row, err := repo.Sqlhandler.Query( "INSERT INTO users (FirstName, LastName) VALUES ($1,$2) RETURNING id;", u.FirstName, u.LastName, ) if err != nil { return } for row.Next() { if err := row.Scan(&id); err != nil { return -1, err } } return } ... 略
UserRepository
にinfrastructure
レイヤで定義したSqlhandler
を埋め込んでいます。
しかし、先ほど説明したように内側の層は外側の層の物を使用してはいけないので新しくSqlhandler
をinterface
レイヤに定義します。/interfaces/database/sqlhandler.gopackage database type Sqlhandler interface { Execute(string, ...interface{}) (Result, error) Query(string, ...interface{}) (Row, error) } type Result interface { LastInsertId() (int64, error) RowsAffected() (int64, error) } type Row interface { Scan(...interface{}) error Next() bool Close() error }
infrastructure
レイヤのSqlhandler
はExecute(string, ...interface{}) (Result, error)
Query(string, ...interface{}) (Row, error)
をもっているのでこれを満たすことができます。
interface
レイヤにSqlhandler
Result
Row
を定義することでinterface
レイヤはinfrastructure
レイヤに依存することはなくなるのです。(Goのインターフェースについてはここ)Usecaseレイヤ
このレイヤでは
interface
レイヤからのInput portの役割、interfaces/controllers
へのGatewayの役割をしている。/usecase/userInteractor.gopackage usecase import "github.com/kikils/golang-todo/domain/model" type UserInteractor struct { UserRepository UserRepository } func (interactor *UserInteractor) Add(u model.User) (id int, err error) { id, err = interactor.UserRepository.Store(u) return } ...略このように
Repository
をもつInteractor
を定義する。Repository
はinterfaces/database
レイヤに存在するので、この層でも先ほどと同じようにインターフェースを定義することで依存関係を解決する。そのためUsecase
レイヤでもRepository
を定義する。/usecase/userRepository.gopackage usecase import "github.com/kikils/golang-todo/domain/model" type UserRepository interface { Store(model.User) (int, error) Update(user model.User) (id int, err error) Delete(userID int) (err error) FindById(int) (model.User, error) FindAll() (model.Users, error) }interfaces/controllersレイヤ
ここのレイヤではリクエストに対するルーティングを実装します。
/interfaces/controllers/userController.gopackage controllers import ( "encoding/json" "io/ioutil" "net/http" "github.com/kikils/golang-todo/domain/model" "github.com/kikils/golang-todo/interfaces/database" "github.com/kikils/golang-todo/usecase" ) type UserController struct { Interactor usecase.UserInteractor } func NewUserController(sqlHandler database.Sqlhandler) *UserController { return &UserController{ Interactor: usecase.UserInteractor{ UserRepository: &database.UserRepository{ Sqlhandler: sqlHandler, }, }, } } func (controller *UserController) Create(w http.ResponseWriter, r *http.Request) { b, err := ioutil.ReadAll(r.Body) if err != nil { ResponseError(w, http.StatusInternalServerError, err.Error()) return } var user model.User if err := json.Unmarshal(b, &user); err != nil { ResponseError(w, http.StatusBadRequest, err.Error()) return } id, err := controller.Interactor.Add(user) if err != nil { ResponseError(w, http.StatusBadRequest, err.Error()) return } ResponseOk(w, id) } ...略
NewUserController
でdatabase.Sqlhandler
を引数に持つのでInteractor
を返してUsecase
レイヤとinfrastructure/database
を紐づけられます。
次にルーティングを実装します。infrastructureレイヤ
リクエストは外部から来るので最外殻の
infrastructure
レイヤにルーティングを実装します。/infrastructure/router.gopackage infrastructure import ( "encoding/json" "log" "net/http" "github.com/kikils/golang-todo/interfaces/controllers" ) func SetUpRouting() *http.ServeMux { mux := http.NewServeMux() sqlhandler := NewSqlhandler() userController := controllers.NewUserController(sqlhandler) todoController := controllers.NewTodoController(sqlhandler) mux.HandleFunc("/user/create", func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodPost: userController.Create(w, r) default: ResponseError(w, http.StatusNotFound, "") } }) mux.HandleFunc("/user/get", func(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodPost: userController.Show(w, r) default: ResponseError(w, http.StatusNotFound, "") } }) ...略
SetUpRouting()
でSqlhandler
を定義しController
に渡してあげることで各レイヤの依存関係を保つことができます。その後、
main.go
に/main.gopackage main import ( "log" "net/http" "github.com/kikils/golang-todo/infrastructure" ) func main() { mux := infrastructure.SetUpRouting() log.Fatal(http.ListenAndServe(":8080", mux)) }として
SetUpRouting()
を呼び出して実装は終了です。最後に
今回はクリーンアーキテクチャを用いてTODOアプリを作成してみました。最初はクリーンアーキテクチャがとっつきにくかったです。実装してみて思ったのは、クリーンアーキテクチャにすることで同じようなコードを複数書く必要があることや、コード補完がきかないので実装のしにくさに気づきました。けれども、DBなどから独立しているので簡単に変更可能だと実感できました。
参考にしたもの
https://qiita.com/ogady/items/34aae1b2af3080e0fec4
https://qiita.com/hirotakan/items/698c1f5773a3cca6193e
https://github.com/eminetto/clean-architecture-go
https://github.com/cohhei/go-to-the-handson/tree/master/04
https://yyh-gl.github.io/tech-blog/blog/go_web_api/
- 投稿日:2020-09-15T15:27:18+09:00
Google の protobuf で gRPC Server に REST のエンドポイントをマッピング
Google が公開している googleapis1 のソースを使うと REST と gRPC の両プロトコルをサポートできるのでご紹介。
どうしても、HTTP/1.1 で通信したいという局面で知っておくと便利。わたしの場合、ALBで gRPC Server にフォワーディングするとき、ターゲットグループのヘルスチェック用のエンドポイントとして実装した。
そのときは envoy を使って Proxy 立てたのだけれど、今回は grpc-gateway を使ってくことにする。
とりあえず gRPCサーバーの準備
すぐ動くのが欲しかったので grpc-go の helloworld を拝借。
git clone https://github.com/grpc/grpc-go # 以降も基本的にこのディレクトリで作業する cd grpc-go/examples/helloworld/ # gRPCサーバー起動 go run greeter_server/main.go # クライアントコード実行で動作確認 go run greeter_client/main.go 2020/09/14 23:02:48 Greeting: Hello world.proto ファイルの中身はこんな感じになっている。
grpc-go/examples/helloworld/helloworld.protosyntax = "proto3"; option go_package = "google.golang.org/grpc/examples/helloworld/helloworld"; option java_multiple_files = true; option java_package = "io.grpc.examples.helloworld"; option java_outer_classname = "HelloWorldProto"; package helloworld; // The greeting service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {} } // The request message containing the user's name. message HelloRequest { string name = 1; } // The response message containing the greetings message HelloReply { string message = 1; }REST API マッピング
helloworld.proto 修正
annotations.proto
を import して rpc の定義にoption (google.api.http) {...}
を追記。grpc-go/examples/helloworld/helloworld.protosyntax = "proto3"; option go_package = "google.golang.org/grpc/examples/helloworld/helloworld"; option java_multiple_files = true; option java_package = "io.grpc.examples.helloworld"; option java_outer_classname = "HelloWorldProto"; // 追記 import "google/api/annotations.proto"; package helloworld; // The greeting service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) { // 追記 option (google.api.http) = { get: "/say_hello" }; } } // The request message containing the user's name. message HelloRequest { string name = 1; } // The response message containing the greetings message HelloReply { string message = 1; }googleapis のソースを取ってくる
go get -u github.com/googleapis/googleapis
コンパイル時に参照したいだけなので
git clone
とかでもOK変更した protobuf を元に自動生成
コンパイラプラグインインストール
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway go get -u github.com/golang/protobuf/protoc-gen-gogRPC Server 向け
このとき
google/api/annotations.proto
を参照できるように protoc の引数に-I
のオプションを追加。 また、既存のhelloworld_grpc.pb.go
と生成されたクライアントコードが重複するので申し訳ないが今回は消してしまう。protoc \ -I. -I$GOPATH/src/github.com/googleapis/googleapis \ --go_out=plugins=grpc:. --go_opt=paths=source_relative \ helloworld/helloworld.proto // 申し訳ないが消す rm helloworld/helloworld_grpc.pb.goProxy Server 向け
grpc-gateway
のコンパイラプラグインを使って同様に自動生成。protoc -I. -I$GOPATH/src/github.com/googleapis/googleapis \ --grpc-gateway_out=logtostderr=true,paths=source_relative:. \ helloworld/helloworld.protogRPC Server の実装を修正
生成された
helloworld.pb.go
に合わせてサーバーの実装を修正。grpc-go/examples/helloworld/greeter_server/main.gopackage main import ( "context" "log" "net" "google.golang.org/grpc" pb "google.golang.org/grpc/examples/helloworld/helloworld" ) const ( port = ":50051" ) // 追記 type server struct {} // 修正: ポインタレシーバに修正 func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) { log.Printf("Received: %v", in.GetName()) return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil } func main() { lis, err := net.Listen("tcp", port) if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() // 修正: server を代入 pb.RegisterGreeterServer(s, &server{}) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } }Proxy Server 実装
grpc-go/examples/helloworld/greeter_proxy/main.gopackage main import ( "flag" "net/http" "github.com/golang/glog" "github.com/grpc-ecosystem/grpc-gateway/runtime" "golang.org/x/net/context" "google.golang.org/grpc" gw "google.golang.org/grpc/examples/helloworld/helloworld" ) var ( tasklistEndpoint = flag.String("tasklist_endpoint", "localhost:50051", "endpoint of YourService") ) func run() error { ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() mux := runtime.NewServeMux() opts := []grpc.DialOption{grpc.WithInsecure()} err := gw.RegisterGreeterHandlerFromEndpoint(ctx, mux, *tasklistEndpoint, opts) if err != nil { return err } return http.ListenAndServe(":8080", mux) } func main() { flag.Parse() defer glog.Flush() if err := run(); err != nil { glog.Fatal(err) } }動作検証
POSTやリクエストヘッダーの設定もできるみたいなので気になる方はどうぞ。
https://grpc-ecosystem.github.io/grpc-gateway/参考
- 投稿日:2020-09-15T13:35:04+09:00
Fargateのスケールアウトを高速化する
はじめに
ALB+Fargate
という構成で動くWebサービスのスケールアウトが遅く、メディア露出や
DM配信によるスパイクアクセスに耐えられない、という問題があり改善策を検討していた
ところ下記記事を見つけました。爆速でFargateをスケールさせる「aws-fargate-fast-autoscaler」を試してみた
記事抜粋簡単に動作原理を説明すると、Fargateをオートスケールさせる場合、通常はECSのターゲット追跡 スケーリングポリシーなどを利用しECSサービスにおけるCloudWatchメトリクスとアラームを利用して タスク数などを制御します。 ただ、この場合、CloudWatchメトリクスからアラーム発報までにどうしてもタイムラグが有り数秒単位 でのスケーリングが難しく基本は分単位でのスケーリングとなっていました。 そこを、この「aws-fargate-fast-autoscaler」では、Step Functionsを利用して3秒毎に Fargateタスクへのコネクション数を取得し、その結果に応じて即ecs service updateでタスク数の 上限を引き上げることで、ターゲット追跡スケーリングポリシーでは実現できない高速なスケーリングを 実現しています。これを導入するか検討した結果、後述する理由により自作ツールを作ることにしました。
(nginxのstub_statusで検知するというアイデアは面白いのでそのまま活用しています!)
この記事はそのツール紹介です。自作する理由
- 最大実行時間の制限が1年
- Step Functions版は、1年を超えて連続稼働できない
- コスト抑えたい
- Step Functions版は、月あたり約65USDかかる
- 自作ツールだと月額11USD程度
- 複数サービスに対応したい
- Step Functions版は、対象URLが1つ
- 弊社では
ALB+Fargate
構成の複数Webサービスを運用しているためfast-autoscaler紹介
概要
/nginx-status
(stub_status)に定期的にアクセスし、Active Connectionsの値を取得し
閾値を超えていた場合にスケールアウトルールに従いタスク数を変更し、Slack通知します。対象となるURL/閾値/Slack通知要WebhookURLは、起動時にパラメータストアから取得します。
コードはこちら
処理フロー
- コンテナ起動時にパラメータストアから設定情報(json形式,詳細後述)を取得
- 指定された
/nginx-status
URLにリクエスト送信し、Active Connectionsの値を確認- 閾値を超えていた場合)
->スケールアウト実行->/nginx-status
チェック停止->猶予期間経過後チェック再開
- 閾値を超えていない場合)
->一定周期での/nginx-status
チェックを継続処理イメージ
使い方
セットアップ手順
CloudFormationテンプレートを使用して、パラメータストア、IAMロール、ECSタスク定義、
ECSクラスタ、ECSサービスを作ります。
CloudFormationテンプレートをダウンロード
curl -sLO https://raw.githubusercontent.com/senbazuru/fast-autoscaler/master/cfn-template.ymlCloudFormationコンソールで上記テンプレートを使用してスタックを作成
作成した時点ではパラメータストアの設定が空なので、CloudWatchLogsにはエラーが
出ている状態ですが問題ありません。
パラメータストアの設定を編集
/ecs/fast-autoscaler/config.json
という名前のパラメータを編集- 後述する設定情報の説明を参照して書き換えて保存
設定変更を反映
- awscliでタスクを停止(その後自動的に起動されることで設定が反映される)
export AWS_PROFILE={AWSクレデンシャルのプロファイル名} TASK_ID=$(aws ecs list-tasks --cluster fast-autoscaler | jq -r '.taskArns[]' | awk -F/ '{print $NF}') aws ecs stop-task --cluster fast-autoscaler --task ${TASK_ID}設定情報
- json形式でパラメータストアに保存
- タイプはString or SecureStringどちらでもOK
- 上述CFnで作成した場合はStringになります
設定例{ "Services": [ { "StatusUrl":"https://example-a.com/nginx-status", "StatusAuthName":"Status-Auth-Key", "StatusAuthValue":"hogefuga", "ScaleoutThreshold":100, "EcsClusterName":"example-app", "EcsServiceName":"example-app", "SlackWebhookUrl":"https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/xxxxxxxx" }, { "StatusUrl":"https://example-b-1234567890.ap-northeast-1.elb.amazonaws.com/nginx-status", "ScaleoutThreshold":150, "MinDesiredCount":2, "CheckInterval":10, "EcsClusterName":"example-app-b", "EcsServiceName":"example-app-b" } ] }
フィールド名 説明 必須 デフォルト 例 StatusUrl nginx-statusのURL ○ https://example-a.com/nginx-status StatusAuthName 認証用HTTPヘッダ名 Status-Auth-Key StatusAuthValue 認証用HTTPヘッダ値 hogefuga ScaleoutThreshold スケールアウト発動するActiveConnectionsの閾値 150 150 MinDesiredCount スケールアウト時に現在のタスク数とみなす最小数 5 5 CheckInterval nginx-statusをチェックする間隔(秒) 3 3 EcsClusterName ECSクラスタ名 ○ example-app EcsServiceName ECSサービス名 ○ example-app SlackWebhookUrl Slack通知用WebhookURL https://hooks.slack.com/services/XXXXXXXXX/XXXXXXXXX/xxxxxxxx 補足
- StatusUrlには
nginxに到達できるドメイン+stub_statusパス
を指定してください
- verifyをスキップするので、SSL証明書のコモンネームとの不一致は許容されます
(つまり、ALBドメイン名を直接指定可能)- スケールアウトは現在のタスク数を2倍にします
- ただし、現在のタスク数が1の場合に2倍にしても2にしかならずスケール不足となる可能性があります
その場合はMinDesiredCountを指定してください。この値をタスク数の下限値として動作します
例)現在タスク数=3、MinDesiredCount=5の場合、3<5なので現在のタスク数を5とみなし、その2倍の10にDesiredCountを変更します。
- StatusAuthName/StatusAuthValueで指定した値が
/nginx-status
へのリクエストにヘッダとして追加されます
- fast-autoscaler以外から
/nginx-status
へのリクエストを防ぐため
ALBルーティング設定のヘッダ認証で使用します- SlackWebhookUrlに指定があればスケールアウト時にSlackに通知を行います
効果(例)
リクエスト数が急増し負荷がかかったために応答速度が遅延し始めていますが、その直後に
fast-autoscalerがスケールアウト(タスク数2->10)したことですぐに正常な応答速度に
戻っていることがわかります!
その後は、ターゲット追跡スケーリングポリシーでタスク数が徐々に減っています。
上がCPU/Mem使用率(左軸)
、タスク数(右軸)
下が応答時間
のグラフまとめ
これで安心して眠れます!
もしよければお使いください。
- 投稿日:2020-09-15T00:36:49+09:00
A Tour of Go メモ 【5】 三日目
A Tour of Go
Methods
関数
引数 func ScaleFunc(v Vertex, i int) func ScaleFunc(v *Vertex, i int) 変数(n) ◯ × ポインタ(*n) ◯ × アドレス(&p) × ◯ メソッド
レシーバ func (v Vertex)) func (v *Vertex) 変数(n) ○ ◯ アドレス(&p) ◯ ◯ struct と type について
Goを学びたての人が誤解しがちなtypeと構造体についてtype Vertex struct { X, Y float64 } type Name struct { first, last string } func(n Name) Greet() string { //変数.値でメソッド内で構造体の値を使うことができる return "I'm " + n.first + " " + n.last } func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { v := Vertex{3, 4} fmt.Println(v.Abs()) n := Name{"first", "last"} fmt.Println(n.Greed()) } > 5 > I'm first last #通常の関数としてもかける type Name struct { first, last string } func Greet(n Name) string { return "I'm " + n.first + " " + n.last } func main() { n := Name{"first", "last"} fmt.Println(Greet(n)) } > I'm first last *任意の型にメソッドを宣言できる type MyFloat float64 func (f MyFloat) Abs() float64 { if f < 0 { return float64(-f) } return float64(f) } func main() { f := MyFloat(-math.Sqrt2) fmt.Println(f.Abs()) }ポインタレシーバ【1】
構造体も引数にされた関数内での値はコピーされたものなので、関数内の変更は関数外の元の構造体に反映されない。ポインタを引数に指定すれば、関数内の変更も元の構造体に反映される
https://www.yoheim.net/blog.php?q=20170902
ポインタについて学ぶ(基本的なところ)関数の引数に値を渡した場合には、関数が実行される際に値がコピーされ、参照先のアドレスが変わります。
そのため、以下のように関数内で値を変更したとしても、呼び出し元の変数には値が反映されません。ポインタレシーバを使う理由
import ( "fmt" "math" ) type Vertex struct { X, Y float64 } func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func (v *Vertex) Scale(f float64) { v.X = v.X * f v.Y = v.Y * f } func main() { v := Vertex{3, 4} fmt.Println(v.X) v.Scale(10) fmt.Println(v.X) fmt.Println(v.Abs()) } > 50 *ポインタを使わないと関数内の変更が反映されない func (v Vertex) Scale(f float64) { // そもそもここのvは元の構造体と違う別物、値はコピーされる // コピーした値を変更し使用しているため、元の構造体の値は変更されない // この関数内でのみ、変更された値を使うことができる v.X = v.X * f v.Y = v.Y * f } func main() { v := Vertex{3, 4} v.Scale(10) //関数外なので、XとYの値は最初に宣言した3,4のままである fmt.Println(v.Abs()) } > 5ポインタレシーバ【2】
func Abs(v Vertex) float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func Scale(v Vertex, f float64) { v.X = v.X * f v.Y = v.Y * f } func main() { v := Vertex{3, 4} // &vはvのアドレスであるため、エラーなる // Scale(v, 10)ならエラーにならないが、vの値の変更は関数内のみ適用される Scale(&v, 10) fmt.Println(Abs(v)) // 例えば、 n := &として引数に*nとするとエラーにならないが、 //しかし同様にポインタレシーバでないので、Vertexの値は変わらない Scale(*n, 10) fmt.Println(Abs(v)) } // Scaleメソッドはポインタレシーバではない // つまりScaleメソッドは、ポインタを引数に取らないためエラーになる > cannot use &v (type *Vertex) as type Vertex in argument to Scale //Scale関数内では値は変わるが、関数外では元のVertexの値は変わらない >5
- ポインタレシーバであった場合は、レシーバ(呼び出し元)が変数でもポインタでもok(変数の場合は自動で"&変数"として解釈してくれる)
type Vertex struct { X, Y float64 } //*Vertex型(Vertexへのポインタである *Vertex型) func (v *Vertex) Scale(f float64) { v.X = v.X * f v.Y = v.Y * f } func ScaleFunc(v *Vertex, f float64) { v.X = v.X * f v.Y = v.Y * f } func main() { v := Vertex{3, 4} // 値のアドレスでなくでもエラーにならない(vを"&v"として自動で解釈してくれている) v.Scale(2) fmt.Println(v) ScaleFunc(&v, 10) fmt.Println(v) // もちろん、こちらはアドレスを渡しているので、ok p := &Vertex{4, 3} p.Scale(3) fmt.Println(v) ScaleFunc(p, 8) fmt.Println(v) fmt.Println(v, p) }ポインタレシーバ【3】
・復習
"&変数"でアドレスを取得
"*変数"でアドレスを元に参照先の値を取得//関数1 // 引数に変数を指定しているのに、ポインタをいれるとエラー func Scale(v Vertex, f float64) { v.X = v.X * f v.Y = v.Y * f } func main() { v:= Vertex{4, 3} fmt.Println(Scale(&v)) } //関数2 //関数の引数にポインタを指定したら、関数の引数は変数でもポインタでもOK func Scale(v *Vertex, f float64) { v.X = v.X * f v.Y = v.Y * f } func main() { n = Vertex{5, 4} fmt.Println(Scale(n)) v:= &Vertex{4, 3} fmt.Println(Scale(v)) } //メソッド //メソッドを設定時、レシーバに変数を指定したら、メソッド実行時のレシーバは変数はもちろん、ポインタ変数でもOK //・・・レシーバ(メソッドの呼び出し元、今回はVertex) func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func AbsFunc(v Vertex) float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { v := Vertex{3, 4} //レシーバ(呼び出し元)が変数vなのでOK fmt.Println(v.Abs()) fmt.Println(AbsFunc(v)) p := &Vertex{4, 3} // レシーバがポインタ変数(p=&Vertex)でもOK fmt.Println(p.Abs()) fmt.Println(AbsFunc(*p)) }
- 投稿日:2020-09-15T00:36:49+09:00
A Tour of Go メモ 【4】
A Tour of Go
Methods
type Vertex struct { X, Y float64 } type Name struct { first, last string } func(n Name) Greet() string { //変数.値でメソッド内で構造体の値を使うことができる return "I'm " + n.first + " " + n.last } func (v Vertex) Abs() float64 { return math.Sqrt(v.X*v.X + v.Y*v.Y) } func main() { v := Vertex{3, 4} fmt.Println(v.Abs()) n := Name{"first", "last"} fmt.Println(n.Greed()) } > 5 > I'm first last #通常の関数としてもかけるtype Name struct {
first, last string
}func Greet(n Name) string {
return "I'm " + n.first + " " + n.last
}func main() {
n := Name{"first", "last"}
fmt.Println(Greet(n))
}I'm first last