やらねば。(風立ちぬ)
案件でGoを使った開発にシフトしつつあるので必死こいて勉強してるわけですが、AWS LambdaをGoで実装したことがなかったのでちょっと触ってみました、というお話。
Table of Contents
AWS LambdaがGoで動くことを知ってますか?
知っている人も多いと思いますが、2017年のre:Invent 2017(AWSのカンファレンスイベント)でLambdaに関するアップデートのなかでGoで動くようになったよ~というのがありました。[速報]AWS Lambdaが機能強化。.NETとGo言語をサポート、サーバレスアプリケーションのリポジトリも登場。AWS re:Invent 2017
Lambda自体は、裏側の基盤にAWS Firecrackerを導入したことがきっかけで、集約化と安全性がめちゃんこあがったので、タイムアウトが長くなったり、カスタムランタイムに対応したりと一気に進化したイメージがありましたが、いまだにPythonか、Node.jsで書くかしかして無かったです。(怠け)
どこかでFirecrackerをいじりたいですね。
仕事上使う機会に恵まれたので今回Go Lambdaをはじめて触ることにしました。
今回の開発スコープ
今回はお勉強というか、感触をつかむためにやるだけなのでちゃんとしたサービスは作りません。
別件で**GitHub API(GraphQL)**を触る必要もあったのでまとめてやってしまいます。
やること
main.goのみ
- RepositoryとかModelとかUsecaseとかそういうのは作らないよ
GitHub APIv4 GraphQLを使うよ
- とりあえず自分の公開されてるレポジトリの使用言語を一覧取るよ
手でビルドし、手でデプロイするよ
とりあえずコード
これが作ったショボコードです。追って解説します。
package main
import (
"context"
"fmt"
"github.com/shurcooL/githubv4"
"golang.org/x/oauth2"
"github.com/deckarep/golang-set"
"github.com/aws/aws-lambda-go/lambda"
)
type Language struct {
Name string
Color string
}
type Repository struct {
Name string
Languages struct {
Nodes []struct {
Language `graphql:"... on Language"`
}
} `graphql:"languages(first: 100)"`
}
var query struct {
Search struct {
Nodes []struct {
Repository `graphql:"... on Repository"`
}
} `graphql:"search(first: 100, query: $q, type: $searchType)"`
}
func getLangList () (mapset.Set){
src := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: "7xxxxxxxxxxxxxxxxxxxxxxxx"},
)
httpClient := oauth2.NewClient(context.Background(), src)
client := githubv4.NewClient(httpClient)
langlist := mapset.NewSet()
variables := map[string]interface{}{
"q": githubv4.String("user:tubone24"), //検索するuser名
"searchType": githubv4.SearchTypeRepository,
}
err := client.Query(context.Background(), &query, variables)
if err != nil {
// Handle error.
fmt.Println(err)
}
for _, repo := range query.Search.Nodes {
fmt.Println("---------")
fmt.Println(repo.Name)
for _, lang := range repo.Languages.Nodes {
fmt.Println(lang.Name)
langlist.Add(lang.Name)
}
}
return langlist
}
func LambdaHandler () (string, error){
result := getLangList()
return fmt.Sprint(result), nil
}
func main() {
lambda.Start(LambdaHandler)
}
GraphQLをGoで叩く
Goはまぎれもなくサーバーサイドな言語なのでどちらかというと関心事がGraphQLのサーバーサイド実装でBFFたくさん作るのめんどくさいからまとめてGraphQLで返したい!という感じのモチベーションの記事が多めではありますが、ちゃんとGraphQLクライアントみつけました。
さらに、GitHubAPIv4専用のクライアントも見つけましたのでこっちを使うことにします。
話は逸れますがGitHubAPIv4はGitHubのPersonal access tokens でAccess Tokenを発行する必要があります。
New personal access tokenから発行できます。発行しておきましょう。shurcooL/githubv4でも使います。
shurcooL/githubv4自体の使い方はそこまで難しくなく、HttpClientやAuthをすませた後、GraphQLのクエリをGoの構造体として定義して投げつければよいです。
このようなGraphQLなら・・
{
search(query: "user:tubone24", type: REPOSITORY, first: 100) {
edges {
node {
... on Repository {
name
languages(first: 100) {
edges {
node {
name
color
}
}
}
}
}
}
}
}
下記のようにすれば取得できます。
//main.go
import (
"context"
"fmt"
"golang.org/x/oauth2"
"github.com/shurcooL/githubv4"
)
// 構造体でGraphQL定義
type Language struct {
Name string
Color string
}
type Repository struct {
Name string
Languages struct {
Nodes []struct {
Language `graphql:"... on Language"`
}
} `graphql:"languages(first: 100)"`
}
var query struct {
Search struct {
Nodes []struct {
Repository `graphql:"... on Repository"`
}
} `graphql:"search(first: 100, query: $q, type: $searchType)"`
}
func hoge () {
src := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: "7xxxxxxxxxxxxxxxxxxxxxxxx"},
) //AccessTokenを設定
httpClient := oauth2.NewClient(context.Background(), src) //AccessTokenをhttpClientに設定
client := githubv4.NewClient(httpClient) //先ほど作ったhttpClient使ってclientを作成
variables := map[string]interface{}{
"q": githubv4.String("user:tubone24"), //検索するuser名
"searchType": githubv4.SearchTypeRepository,
}
err := client.Query(context.Background(), &query, variables) //client.Queryで実行。エラーのみが戻りで実行結果は咲くほど定義した構造体に格納
if err != nil {
// Handle error.
fmt.Println(err)
}
for _, repo := range query.Search.Nodes {
fmt.Println("---------")
fmt.Println(repo.Name)
for _, lang := range repo.Languages.Nodes {
fmt.Println(lang.Name)
fmt.Println(lang.Color)
}
}
}
あらまぁ、簡単!と思ったものの、上記のようにNodesは取得できたのですが、Edgesに定義された値がどうやってもとれない。。
例えば・・
{
search(query: "user:tubone24", type: REPOSITORY, first: 100) {
edges {
node {
... on Repository {
name
languages(first: 100) {
edges {
size node {
name
color
}
}
}
}
}
}
}
}
のようにedgesに項目がありnodeを取りたい場合、
type Language struct {
Name string
Color string
}
type Repository struct {
Name string
Languages struct {
Edges []struct { Node struct { Language `graphql:"... on Language"` } } } `graphql:"languages(first: 100)"`
}
var query struct {
Search struct {
Nodes []struct {
Repository `graphql:"... on Repository"`
}
} `graphql:"search(first: 100, query: $q, type: $searchType)"`
}
とやってもエラーになってしまう・・。
こちらのPRの通りEdgesは構造体のスライスで宣言してね~というのも試したのですがうまくいかず。。
使用言語のサイズが取りたいだけなんですよ…。
今回は趣旨から反するのでいったん塩漬け。。
LambdaでGoを使うとき
main関数にはAWSが用意している github.com/aws/aws-lambda-go/lambda
からロジックを Invokeさせないと問答無用でLambdaでエラーになってしまいます。
そこで github.com/aws/aws-lambda-go/lambda
の lambda.Start
を使って動かします。
func LambdaHandler () (string, error){
result := hoge() //login
return fmt.Sprint(result), nil
}
func main() {
lambda.Start(LambdaHandler)}
AWS Lambdaにデプロイする
GoをLambdaにデプロイするときは、実行ファイルにBuildしたものをZIPで固めてあげます。
Lambda画面のCloud9から編集できないんですね・・・
実行ファイル、ということはビルドするプラットフォーム(OSとか)に依存してしまうのでは?と思ったのですが、 ベストプラクティスとして GOOS=linux
をgo build時につけることでLinux互換な実行ファイルになるみたいです。
GOOS=linux go build main.go
あとは実行ファイルをZIPで固めて、Lambda作ってアップロードして保存すれば終わりです。
実行
Lambdaのテスト実行をしてみます。
無事、GitHubの私のレポジトリ群の言語一覧が取れました。
printしているものはCloudwatchにも出てきていました。(goのlogを使ってもきちんとCWにログ出るそうです。)
ひとまず完成っぽいです。
おまけ
上記のコードでは、レポジトリ群に重複した言語があった場合は重複を避ける形で出力しています。
PythonではSetという便利なものがあるのですが、Goではあるのでしょうか・・・。
ありました。
import (
"fmt"
"github.com/deckarep/golang-set")
// 中略・・
func main () {
langlist := mapset.NewSet() // setを作る // 中略
for _, repo := range query.Search.Nodes {
fmt.Println("---------")
fmt.Println(repo.Name)
for _, lang := range repo.Languages.Nodes {
fmt.Println(lang.Name)
langlist.Add(lang.Name) //setにAddする }
}
return langlist // set{hoge, fuga} 重複がないsetが返る
}
便利!
こういう便利なもの、もっと作っていきたいですね。
使ってみての感想
GoでLambdaを組んでみての感想は、
- Cloud9で直接Lambda編集したいなぁ…
- lambda.startにラッピングする必要があるのでローカルで確認しにくいなぁ
- こちら解決する方法は次回考えます
- 果たして早くなったのか?
- わからん。調べたい