- 投稿日:2020-02-12T13:33:22+09:00
Go の gRPC で Redis のデータを削除 (Delete)
設定ファイル、サーバープログラム、クライアントプログラムの3つが必要です。
$ tree . ├── redis_delete │ └── redis_delete.proto ├── redis_delete_client │ └── main.go └── redis_delete_server └── main.go設定ファイル
redis_delete/redis_delete.proto
こちらと同じ
Python の gRPC で Redis のデータを削除 (Delete)サーバープログラム
redis_delete_server/main.go// --------------------------------------------------------------- // // redis_delete_server/main.go // // Feb/11/2020 // --------------------------------------------------------------- package main import ( "context" "log" "net" "fmt" "os" "google.golang.org/grpc" pb "../redis_delete" ) const ( port = ":50051" ) type server struct { pb.UnimplementedGreeterServer } // --------------------------------------------------------------- func redis_delete_proc (key_in string) string { rvalue := key_in hostname := "localhost" port := "6379" conn, err := net.Dial ("tcp", hostname + ":" + port) if err != nil { fmt.Println(err) return rvalue } _, err = conn.Write ([]byte("del " + key_in + "\r\n")) if err != nil { fmt.Println(err) return rvalue } conn.Close () return rvalue } // --------------------------------------------------------------- func (s *server) RedisDelete(ctx context.Context, in *pb.RedisRequest) (*pb.RedisReply, error) { fmt.Fprintf (os.Stderr,"*** check aaa ***\n") key := in.GetKey() fmt.Fprintf (os.Stderr,"key = " + key + "\n") rvalue := redis_delete_proc (key) return &pb.RedisReply{Key: rvalue }, nil } // --------------------------------------------------------------- func main() { lis, err := net.Listen("tcp", port) if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } } // ---------------------------------------------------------------クライアントプログラム
redis_delete_client/main.go// --------------------------------------------------------------- // // redis_delete_client/main.go // // Feb/11/2020 // // --------------------------------------------------------------- package main import ( "context" "log" "os" "time" "google.golang.org/grpc" pb "../redis_delete" ) const ( address = "localhost:50051" defaultKey = "t0001" ) // --------------------------------------------------------------- func main() { // Set up a connection to the server. conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock()) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() c := pb.NewGreeterClient(conn) // Contact the server and print out its response. key := defaultKey if len(os.Args) > 1 { key = os.Args[1] } ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() r, err := c.RedisDelete(ctx, &pb.RedisRequest{Key: key}) if err != nil { log.Fatalf("could not greet: %v", err) } rvalue := r.GetKey() log.Printf("Rvalue: %s", rvalue) } // ---------------------------------------------------------------gRPC のコードを作成します。
スクリプト
protoc -I redis_delete redis_delete/redis_delete.proto --go_out=plugins=grpc:redis_deleteサーバープログラムの起動
go run redis_delete_server/main.goクライアントプログラムの実行
$ go run redis_delete_client/main.go t1852 2020/02/12 13:33:08 Rvalue: t1852
- 投稿日:2020-02-12T13:26:37+09:00
Go の gRPC で Redis のデータを更新 (Update)
設定ファイル、サーバープログラム、クライアントプログラムの3つが必要です。
$ tree . ├── redis_update │ └── redis_update.proto ├── redis_update_client │ └── main.go └── redis_update_server └── main.go設定ファイル
redis_update/redis_update.proto
こちらと同じ
Python の gRPC で Redis のデータを更新 (Update)サーバープログラム
redis_update_server/main.go// --------------------------------------------------------------- // // redis_update_server/main.go // // Feb/11/2020 // --------------------------------------------------------------- package main import ( "context" "log" "net" "fmt" "os" "encoding/json" "time" "strconv" "strings" "google.golang.org/grpc" pb "../redis_update" ) const ( port = ":50051" ) type server struct { pb.UnimplementedGreeterServer } // --------------------------------------------------------------- func get_current_date_proc () string { now := time.Now () fmt.Printf ("%s\t" ,now) fmt.Printf ("%d-%d-%d\n" ,now.Year (),now.Month(),now.Day()) today := strconv.Itoa (now.Year()) + "-" + fmt.Sprintf ("%d",now.Month()) + "-" + strconv.Itoa (now.Day()) return today } // --------------------------------------------------------------- func json_update_proc (json_str string,population_in int) string { var unit_aa map[string]interface{} json.Unmarshal ([]byte(json_str), &unit_aa) fmt.Printf ("%s\t",unit_aa["name"]) fmt.Printf ("%f\t",unit_aa["population"]) fmt.Printf ("%s\n",unit_aa["date_mod"]) unit_aa["population"] = population_in unit_aa["date_mod"] = get_current_date_proc () output, _ := json.Marshal(unit_aa) json_str = string(output) return json_str } // --------------------------------------------------------------- func redis_socket_write_proc (conn net.Conn,key_in string,json_str string) { fmt.Println (key_in) fmt.Println (json_str) comm_aa := "set " + key_in + " '" + json_str + "'\r\n" conn.Write([]byte(comm_aa)) buf := make ([]byte,1024) conn.Read (buf[:]) fmt.Println (string(buf[0:10])) } // --------------------------------------------------------------- func socket_read_proc (conn net.Conn,key_in string) string { str_received := "" _, err := conn.Write([]byte("get " + key_in + "\r\n")) if err != nil { fmt.Println(err) return str_received } buf := make([]byte, 1024) nn, err := conn.Read(buf[:]) if err != nil { fmt.Println(err) return str_received } str_received = string(buf[0:nn]) return str_received } // --------------------------------------------------------------- func redis_socket_read_proc (conn net.Conn,key_in string) string { json_str := "" str_received := socket_read_proc (conn,key_in) lines := strings.Split(str_received,"\n") if (! strings.Contains(lines[0],"END")) { json_str = lines[1] } return json_str } // --------------------------------------------------------------- func redis_update_proc (key_in string,population int32) string { str_json := "" hostname := "localhost" port := "6379" conn, err := net.Dial ("tcp", hostname + ":" + port) if err != nil { fmt.Println(err) return str_json } str_json = redis_socket_read_proc (conn,key_in) fmt.Fprintf (os.Stderr,"str_json = " + str_json + "\n") str_json_new := json_update_proc (str_json,int(population)) fmt.Println (str_json_new) redis_socket_write_proc (conn,key_in,str_json_new) return key_in } // --------------------------------------------------------------- func (s *server) RedisUpdate(ctx context.Context, in *pb.RedisRequest) (*pb.RedisReply, error) { // log.Printf("SayHello Received: %v", in.GetName()) fmt.Fprintf (os.Stderr,"*** check aaa ***\n") key := in.GetKey() population := in.GetPopulation() fmt.Fprintf (os.Stderr,"key = " + key + "\n") fmt.Fprintf (os.Stderr,"population = %d\n" , population) rkey := redis_update_proc (key,population) return &pb.RedisReply{Key: rkey }, nil } // --------------------------------------------------------------- func main() { lis, err := net.Listen("tcp", port) if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } } // ---------------------------------------------------------------クライアントプログラム
redis_update_client/main.go// --------------------------------------------------------------- // // redis_update_client/main.go // // Feb/11/2020 // // --------------------------------------------------------------- package main import ( "context" "fmt" "log" "os" "strconv" "time" "google.golang.org/grpc" pb "../redis_update" ) const ( address = "localhost:50051" defaultKey = "t0001" defaultPopulation = int32(12345) ) // --------------------------------------------------------------- func main() { // Set up a connection to the server. conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock()) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() c := pb.NewGreeterClient(conn) // Contact the server and print out its response. key := defaultKey population := defaultPopulation if len(os.Args) > 1 { key = os.Args[1] ppt,_ := strconv.Atoi (os.Args[2]) population = int32(ppt) } ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() r, err := c.RedisUpdate(ctx, &pb.RedisRequest{Key: key,Population: population}) if err != nil { log.Fatalf("could not greet: %v", err) } rvalue := r.GetKey() fmt.Printf ("%s\n",rvalue) } // ---------------------------------------------------------------gRPC のコードを作成します。
スクリプト
protoc -I redis_update redis_update/redis_update.proto --go_out=plugins=grpc:redis_updateサーバープログラムの起動
go run redis_update_server/main.goクライアントプログラムの実行
$ go run redis_update_client/main.go t1858 3298700 t1858
- 投稿日:2020-02-12T13:19:35+09:00
Go の gRPC で Redis のデータを読む (Read)
設定ファイル、サーバープログラム、クライアントプログラムの3つが必要です。
$ tree . ├── redis_read │ └── redis_read.proto ├── redis_read_client │ └── main.go └── redis_read_server └── main.go設定ファイル
redis_read/redis_read.protosyntax = "proto3"; package redis_read; service Greeter { rpc RedisRead (RedisRequest) returns (RedisReply) {} } message RedisRequest { string key = 1; } message RedisReply { string strjson = 1; }サーバープログラム
redis_read_server/main.go// --------------------------------------------------------------- // // redis_read_server/main.go // // Feb/11/2020 // --------------------------------------------------------------- package main import ( "context" "log" "net" "fmt" "os" "strings" "google.golang.org/grpc" pb "../redis_read" ) const ( port = ":50051" ) type server struct { pb.UnimplementedGreeterServer } // --------------------------------------------------------------- func socket_read_proc (conn net.Conn,key_in string) string { str_received := "" _, err := conn.Write([]byte("get " + key_in + "\r\n")) if err != nil { fmt.Println(err) return str_received } buf := make([]byte, 1024) nn, err := conn.Read(buf[:]) if err != nil { fmt.Println(err) return str_received } str_received = string(buf[0:nn]) return str_received } // --------------------------------------------------------------- func redis_socket_read_proc (conn net.Conn,key_in string) string { json_str := "" str_received := socket_read_proc (conn,key_in) lines := strings.Split(str_received,"\n") if (! strings.Contains(lines[0],"END")) { json_str = lines[1] } return json_str } // --------------------------------------------------------------- func redis_read_proc (key_in string) string { str_json := "" hostname := "localhost" port := "6379" conn, err := net.Dial ("tcp", hostname + ":" + port) if err != nil { fmt.Println(err) return str_json } str_json = redis_socket_read_proc (conn,key_in) // str_json = key_in + " aaa " + key_in return str_json } // --------------------------------------------------------------- func (s *server) RedisRead(ctx context.Context, in *pb.RedisRequest) (*pb.RedisReply, error) { fmt.Fprintf (os.Stderr,"*** check aaa ***\n") key := in.GetKey() fmt.Fprintf (os.Stderr,"key = " + key + "\n") str_json := redis_read_proc (key) return &pb.RedisReply{Strjson: str_json }, nil } // --------------------------------------------------------------- func main() { lis, err := net.Listen("tcp", port) if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } } // ---------------------------------------------------------------クライアントプログラム
redis_read_client/main.go// --------------------------------------------------------------- // // redis_read_client/main.go // // Feb/11/2020 // // --------------------------------------------------------------- package main import ( "context" "fmt" "strconv" "log" "os" "time" "encoding/json" "google.golang.org/grpc" pb "../redis_read" ) const ( address = "localhost:50051" defaultKey = "t0001" ) // --------------------------------------------------------------- func main() { // Set up a connection to the server. conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock()) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() c := pb.NewGreeterClient(conn) // Contact the server and print out its response. key := defaultKey if len(os.Args) > 1 { key = os.Args[1] } ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() r, err := c.RedisRead(ctx, &pb.RedisRequest{Key: key}) if err != nil { log.Fatalf("could not greet: %v", err) } str_json := r.GetStrjson() // log.Printf("Greeting: %s", str_json) var unit_aa map[string]interface{} json.Unmarshal ([]byte(str_json), &unit_aa) fmt.Printf ("%s\t",key) population := int(unit_aa["population"].(float64)) str_population := strconv.Itoa(population) fmt.Printf ("%s\t",str_population) fmt.Printf ("%s\t",unit_aa["name"]) fmt.Println (unit_aa["date_mod"]) } // ---------------------------------------------------------------gRPC のコードを作成します。
スクリプト
protoc -I redis_read redis_read/redis_read.proto --go_out=plugins=grpc:redis_readサーバープログラムの起動
go run redis_read_server/main.goクライアントプログラムの実行
$ go run redis_read_client/main.go t1855 t1855 17859 勝山 1921-6-24
- 投稿日:2020-02-12T13:11:11+09:00
Go の gRPC で Redis のデータを作成 (Create)
設定ファイル、サーバープログラム、クライアントプログラムの3つが必要です。
$ tree . ├── redis_create │ └── redis_create.proto ├── redis_create_client │ └── main.go └── redis_create_server └── main.go設定ファイル
redis_create/redis_create.protosyntax = "proto3"; package redis_create; service Greeter { rpc RedisCreate (RedisRequest) returns (RedisReply) {} } message RedisRequest { string key = 1; string strjson = 2; } message RedisReply { string key = 1; }サーバープログラム
redis_create_server/main.go// --------------------------------------------------------------- // // redis_create_server/main.go // // Feb/12/2020 // --------------------------------------------------------------- package main import ( "context" "log" "net" "fmt" "os" "google.golang.org/grpc" pb "../redis_create" ) const ( port = ":50051" ) type server struct { pb.UnimplementedGreeterServer } // --------------------------------------------------------------- func redis_socket_write_proc (conn net.Conn,key_in string,json_str string) { fmt.Println (key_in) fmt.Println (json_str) comm_aa := "set " + key_in + " '" + json_str + "'\r\n" conn.Write([]byte(comm_aa)) buf := make ([]byte,1024) conn.Read (buf[:]) fmt.Println (string(buf[0:10])) } // --------------------------------------------------------------- func redis_create_proc (key_in string,str_json string) string { hostname := "localhost" port := "6379" conn, err := net.Dial ("tcp", hostname + ":" + port) if err != nil { fmt.Println(err) return str_json } fmt.Fprintf (os.Stderr,"str_json = " + str_json + "\n") redis_socket_write_proc (conn,key_in,str_json) return key_in } // --------------------------------------------------------------- func (s *server) RedisCreate(ctx context.Context, in *pb.RedisRequest) (*pb.RedisReply, error) { fmt.Fprintf (os.Stderr,"*** check aaa ***\n") key := in.GetKey() str_json := in.GetStrjson() fmt.Fprintf (os.Stderr,"key = " + key + "\n") fmt.Fprintf (os.Stderr,"str_json = " + str_json + "\n") redis_create_proc (key,str_json) return &pb.RedisReply{Key: key }, nil } // --------------------------------------------------------------- func main() { lis, err := net.Listen("tcp", port) if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() pb.RegisterGreeterServer(s, &server{}) if err := s.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } } // ---------------------------------------------------------------クライアントプログラム
redis_create_client/main.go// --------------------------------------------------------------- // // redis_create_client/main.go // // Feb/12/2020 // // --------------------------------------------------------------- package main import ( "context" "fmt" "log" "os" "encoding/json" "strconv" "time" "google.golang.org/grpc" pb "../redis_create" ) const ( address = "localhost:50051" defaultKey = "t0001" defaultName = "aaaa" defaultPopulation = 1 defaultDate_mod = "2000-01-01" ) // --------------------------------------------------------------- func main() { // Set up a connection to the server. conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock()) if err != nil { log.Fatalf("did not connect: %v", err) } defer conn.Close() c := pb.NewGreeterClient(conn) // Contact the server and print out its response. key := defaultKey unit_aa := make(map[string]interface{}) unit_aa["name"] = defaultName unit_aa["population"] = defaultPopulation unit_aa["date_mod"] = defaultDate_mod if len(os.Args) > 1 { key = os.Args[1] unit_aa["name"] = os.Args[2] unit_aa["population"],_ = strconv.Atoi (os.Args[3]) unit_aa["date_mod"] = os.Args[4] } ctx, cancel := context.WithTimeout(context.Background(), time.Second) defer cancel() output, _ := json.Marshal(unit_aa) str_json := string(output) r, err := c.RedisCreate(ctx, &pb.RedisRequest{Key: key,Strjson: str_json}) if err != nil { log.Fatalf("could not greet: %v", err) } rvalue := r.GetKey() fmt.Printf ("%s\n",rvalue) } // ---------------------------------------------------------------gRPC のコードを作成します。
スクリプト
protoc -I redis_create redis_create/redis_create.proto --go_out=plugins=grpc:redis_createサーバープログラムの起動
go run redis_create_server/main.goクライアントプログラムの実行
$ go run redis_create_client/main.go t0934 那須烏山 42938 2003-9-22 t0934
- 投稿日:2020-02-12T09:13:23+09:00
【go + gin + gorm】GAEとCloud SQL for MySQLを使ってデプロイしてみる
【Go+Gin+Gorm】初心者だから超簡単webサービス作ってみる
【go + gin + gorm】webアプリにログイン機能を追加してみる
続きです。今回は、劣化版ツイッターアプリをGCPを使ってデプロイしてみるという内容です。
GCPで使うのは、GAE standard環境
と、Cloud SQL for MySQL
です。
コードはgithubに上げています。
go modules
を使う今回、GAEにデプロイするにあたって、
$GOPATH
周りでエラーが起こったので、go modules
を使うことにしました。
go modules
は依存モジュール管理ツールです。
Go ではGOPATH
という概念があって、Go のコードは外部ライブラリも含めてすべて$GOPATH/src
以下に置くことになっています。
つまり、外部ライブラリと自分のコードが同列の場所にあるということになります。
でもそれだとプロジェクトごとに違うバージョンのライブラリを使いたい時などは困る。開発する度にいちいち外部ライブラリを消して入れ直したりをしなくてはならない。
詳しくは以下のリンク先を読むといいかもしれません。
Go Modules
Go言語の依存モジュール管理ツール Modules の使い方以下のコマンドを実行していきましょう。
$ cd <このプロジェクト名> // 依存モジュールの情報は `go.mod` と `go.sum` という名前のファイルに記載される。 $ go mod init // 依存モジュールの自動インストール $ go buildちなみに、自作パッケージをimport文に記載するときは、
<プロジェクトのディレクトリ名>/<自作パッケージディレクトリ名>
にしないとgo build
のときにエラーになりました。main.go// 今回の場合はこんな感じ mytweet-deploy/cryptoGAEへのデプロイ
goで作られたプログラムがデプロイされる際にGAE上で行われていることは、アプリケーションのビルドに必要なソースコードを
$GOPATH
含めて探索し、すべてアップロードした上でリモートでコンパイルするという流れ。
手元でコンパイルしたアプリケーションのバイナリをアップロードするのではないし、主要なソースコードだけをアップロードしてリモートでgo get
するわけでもないようです。
この辺りは以下のブログで言及されていました。
Google App EngineでGoを動かすときに知っておくべきこと(ソースコード・ビルド編)ここからは
gcloud
コマンドがローカルで使えるようになっていることを前提に話を進めます。
GCP上でプロジェクトを作成してください。
app.yaml
ファイルがある階層にcd
して以下を実行。$ gcloud app deployGAEに他のインスタンスがない場合は、GAEのインスタンス名はdefaultでいいですか?
GAEにdefaultという名前のインスタンスがある場合は、GAEのインスタンス名は何にしますか?
などを聞かれると思うので、入力してください。
何を入力したか忘れてもあとでブラウザ上で確認できます。そのあと
Y/n
という究極の選択を迫られるので、すかさずY
。
数分かかるので、コーヒーでも飲んで休憩してください。
特にエラーが出なければ、ターミナルで以下を実行。$ gcloud app browseブラウザが現れてお馴染みのハローワールドが出てればOK。
嘘です。
エラーが出てたらOKです。
GCPコンソールのApp Engine
上にさっき作ったインスタンス名が存在していればOKです。
エラーが出ているのは、データベースに接続できないからです。
これからCloud SQLにインスタンスを作って、アプリ側からデータベースに接続できるように設定していきます。
つまり、ここまででアプリのデプロイは完了したってことですね。どんなエラーが出てるか見たいねんって方は、以下を実行。
$ gcloud app logs tail -s <インスタンス名>おそらくデータベース接続の部分でエラーが出ているはず。。
本番環境とローカル環境との共存
本番環境のMySQLにつなぐ部分と、ローカル環境のMySQLにつなぐ処理は以下のように記述しました。
appengine
ライブラリのIsAppEngine()
関数で、App EngineアプリがApp Engineで実行されているかどうかをbool型で取得することができます。
これでローカルでもGAE上でも動かすことができます。main.goif appengine.IsAppEngine() { db, err = gorm.Open("mysql", cloudSQLConnection) } else { db, err = gorm.Open("mysql", localConnection) } if err != nil { panic(err.Error()) }
appengine
ライブラリの公式リファレンスCloud SQLにデータベースを作成する。
先ほど言ったように、ただ単にGAEにプログラムをデプロイしてもデータベースがないと今回のプログラムは動きません。
まずは以下のリンクからGoogle Cloud SQL APIを有効にして下さい。
https://cloud.google.com/sql/docs/mysql/admin-api/?hl=jaブラウザでGCPのコンソールを開いてください。
左上のハンバーガーメニューからSQL
をクリック。
インスタンスを作成
をクリック。
MySQL
をクリック。
項目を適当に埋めていきます。
作成
をクリック。
作成されたインスタンスをクリックすると概要というページに飛びます。
左メニューから接続
をクリック。
プライベートIP
にチェックを入れて、関連付けられたネットワーキング
には、先ほどGAEで作ったインスタンス名を選択して下さい。その後保存
をクリック。
次は左メニューからユーザー
をクリック。ここではデータベースにアクセスできるユーザーを作成します。
ユーザーアカウントを作成
をクリック。ユーザー名、パスワードを入力して下さい。保存しましょう。どこかにメモして下さい。
最後に、左メニューからデータベース
をクリック。ここではデータベースを作っておきます。
データベースを作成
をクリックして、名前を付けて保存。文字コードはutf8です。設定は以上です。
概要
ページに戻って、このインスタンスに接続
という欄のインスタンス接続名
をどこかにメモして下さい。
設定を.env
に記入していきます。// インスタンス接続名 mytweet_CONNECTIONNAME=project-id:region-name:instance-name // 作成したユーザー名とパスワード mytweet_USER=username mytweet_PASS=12345678 // 作成したデータベースの名前 mytweet_DBNAME=test保存をしたら、先ほどのようにGAEにデプロイして下さい。
$ gcloud app deploy $ gcloud app browseエラーなく画面が表示されたらデプロイ完了です。
お疲れ様でした。
ユーザー登録してみたり、ログインしてみたりして下さい。Cloud SQLの中身をローカルで確認する
Cloud SQL Proxy を使用して、ローカルからCloud SQLに接続します。
以下MacOS 64ビットの方用です。それ以外の方はローカルマシンに Cloud SQL Proxy クライアントをインストールするを参照ください。// プロキシをダウンロードします。 $ curl -o cloud_sql_proxy https://dl.google.com/cloudsql/cloud_sql_proxy.darwin.amd64 // プロキシを実行できるようにします。 $ chmod +x cloud_sql_proxy // さっきメモしたインスタンス接続名が必要です。 $ ./cloud_sql_proxy -instances=<INSTANCE_CONNECTION_NAME>=tcp:3306 // MySQLを起動します。さっき作ったユーザー名が必要です。 $ mysql -u <USERNAME> -p --host 127.0.0.1 --port 3306これでCloud SQL上のMySQLを確認することができます。
select文でテーブルの中身を確認したりしてみて下さい。こちらがCloud SQL Proxyを導入する際の公式ドキュメントです。
ローカルテストにプロキシを使用する場合のクイックスタート最後に
次はセッションをやろうかなと思ってます。
- 投稿日:2020-02-12T07:15:20+09:00
Golang x Beego x Docker x CircleCI x npmで開発環境をサクッと作ってみよう
DockerとCircleCIなどを組み込みつつGolangのBeego開発環境を構築します。
本稿では以下を前提とします。前提となる知識
・terminalのコマンド操作
・viまたはエディタの操作
・Docker,docker-composeの知識
・gitやgithubの基本的な操作前提となる条件
・OSはmacOSを前提とします。どうしてもWindowsなどの場合はVagrantでLinuxの仮想環境を立てるなどして自力で対応してみてください。
・バージョン管理のツールはGithubを用います。この記事で書いていること
・Beegoの0からの環境構築
・Dockerとdocker-composeでコンテナ環境構築
・CircleCIの0からの設定
・フロントエンド環境の0からの構築この記事に書いてないこと
・Go言語特有の実装方法やtipsなどBeego環境のセットアップ
Goのインストールの確認
以下のコマンドでGoがローカルに入っているか確認しましょう。
go version
not foundと表示される場合は公式サイトからパッケージのダウンロードをするかbrewでインストールしましょう。
インストール後はGOPATHの設定が必要です。
手順としては大体以下ですが、環境によって異なることがあるので公式を確認しましょう。
- GOPATHとなるディレクトリを作成しておく
mkdir ~/go/
- ~/.bash_profileを開く
vim ~/.bash_profile
- 以下を追記する
export GOPATH=$HOME/go export PATH=$PATH:$GOPATH/bin
- 保存後に反映
source ~/.bash_profileBeegoのインストール
Goのインストールが完了してgoコマンドが使えるようになったことを確認できたらBeegoをインストールしましょう。
公式サイトを参考に必要なツールをインストールする。go get -u github.com/astaxie/beego go get -u github.com/beego/bee正しく実行されると
$GOPATH/bin
の配下にバイナリファイルが格納されます。プロジェクトを作成する
- ソースコード格納用のディレクトリの作成(任意)
mkdir $GOPATH/src
- プロジェクトを作成
$ cd $GOPATH/src $ bee new beego-app $ cd beego-app $ ls conf controllers main.go models routers static tests views
- プロジェクトを起動
bee run「アプリケーション“beego-app”へのネットワーク受信接続を許可しますか?」とアラートが表示されると思うが、許可をクリックして http://localhost:8080/ にアクセスして以下のような画面が表示されることを確認する。
ここまでで以下のような構成のプロジェクトが作成されています。
それぞれの役割を詳しく確認したい場合は公式ドキュメントを見てみましょう。. ├── beego-app ├── conf │ └── app.conf ├── controllers │ └── default.go ├── main.go ├── models ├── routers │ └── router.go ├── static │ ├── css │ ├── img │ └── js │ └── reload.min.js ├── tests │ └── default_test.go └── views └── index.tplDockerとdocker-composeでコンテナを立ち上げる
ホスト側でのBeego環境はできましたが、他開発者との共有を簡単に行うことができるようにDockerとdocker-composeを使ってコンテナ環境を立ち上げましょう。
docker-compose.ymlの作成
以下のファイルをプロジェクト直下に配置する
$ touch $GOPATH/src/beego-app/docker-compose.ymldocker-compose.ymlversion: '3' services: app: container_name: beego-app build: context: docker dockerfile: Dockerfile volumes: - .:/go/src/beego-app ports: - 10080:10080portが他コンテナやサービスにバッティングしてしまう場合は、そのポートをlsofコマンドで確認してkillするかportsのところを任意に書き換えてください。
buildで記載されているcontext直下のDockerfileをビルドします。docker-compose について詳しく知りたい場合
http://docs.docker.jp/compose/compose-file.htmlDockerfileの作成
dockerというディレクトリを作成してDockerfileを追加します。
Dockerfileについてはこちらmkdir $GOPATH/src/beego-app/docker touch $GOPATH/src/beego-app/docker/Dockerfile# @see::https://hub.docker.com/_/amazonlinux FROM amazonlinux:2 # システムアップデート RUN yum update -y # gitのインストール RUN yum install -y git # @see::https://fedoraproject.org/wiki/EPEL RUN amazon-linux-extras install -y epel # amazon-linux-extrasでインストールできる最新のgolang RUN amazon-linux-extras list | grep golang RUN amazon-linux-extras install -y golang1.11 RUN go version # beegoのインストール ENV GOPATH /go ENV PATH $PATH:$GOPATH/bin RUN go get -u github.com/beego/bee RUN go get -u github.com/astaxie/beego # カレントディレクトリをコンテナに追加する COPY . /go/src/beego-app # 作業ディレクトリを指定する WORKDIR /go/src/beego-app # コンテナ実行時にコンパイルと実行を行う CMD bee runイメージはAWS EC2での運用を想定してamazonlinux:2を用いている。
amazonlinuxベースのコンテナ上にgoとbeegoをインストールし、ホスト側をコンテナ側にマウントし最後にbeegoを実行しています。コンテナを起動する
コンテナを立ち上げる前に上記のDockerfileのportをmain.goのbeego.Runの引数に渡します。
main.gopackage main import ( _ "beego-app/routers" "github.com/astaxie/beego" ) func main() { beego.Run(":10080") }Dockerfileをビルドしてコンテナを起動します。
docker-compose.ymlのディレクトリで以下のコマンドを実行します。$ docker-compose up -d Creating network "beego-app_default" with the default driver Building app Step 1/14 : FROM amazonlinux:2 2: Pulling from library/amazonlinux正常に起動できているかpsコマンドで確認してみましょう。
$ docker-compose ps Name Command State Ports ----------------------------------------------------------------- beego-app /bin/sh -c bee run Up 0.0.0.0:10080->10080/tcpコンテナの中に入って見てみましょう。
$ docker exec -it beego-app bash bash-4.2# ls beego-app conf controllers docker docker-compose.yml main.go models routers static tests touch views bash-4.2# pwd /go/src/beego-appもし、うまくいかなくてクリーンな状態からやり直したい場合、
docker-compose down --rmi all --volumes
で該当のコンテナ、ネットワーク、ボリューム、イメージだけを削除するか、
もし他のコンテナやイメージもリセットしてしまって良い場合はdockerのアプリケーションを起動してpreferencesから「Reset disk image」で完全にリセットしてしまう方法もあります。上記うまく行っていたらコンテナの実行ログを見てみましょう。
$ docker logs -f beego-app ______ | ___ \ | |_/ / ___ ___ | ___ \ / _ \ / _ \ | |_/ /| __/| __/ \____/ \___| \___| v1.10.0 2020/02/09 10:21:09 INFO ▶ 0001 Using 'beego-app' as 'appname' 2020/02/09 10:21:10 INFO ▶ 0002 Initializing watcher... beego-app/controllers beego-app/routers beego-app 2020/02/09 10:21:12 SUCCESS ▶ 0003 Built Successfully! 2020/02/09 10:21:12 INFO ▶ 0004 Restarting 'beego-app'... 2020/02/09 10:21:12 SUCCESS ▶ 0005 './beego-app' is running... 2020/02/09 10:21:12.975 [I] [asm_amd64.s:1357] http server Running on http://:10080
http server Running on http://:10080
というところに着目して、10080番ポートにリッスンされていることが確認できたので以下にアクセスして確認してみます。CircleCIでCI環境を構築する
最近ではgithub actionsなども出てきているが、とはいえCircleCIを採用している現場もまだまだ多いとおもうので、ここではCircleCIを用いてCI「Continuous Integration(継続的インテグレーション)」環境を構築する。
config.ymlの作成
プロジェクト直下に
.circleci
というディレクトリを作成し、.circleci直下にconfig.ymlを置く。$ mkdir $GOPATH/src/beego-app/.circleci $ touch $GOPATH/src/beego-app/.circleci/config.ymlconfig.ymlを以下のように作成する
config.yml# @see::https://circleci.com/docs/ja/2.0/configuration-reference/ version: 2.1 # 実行処理は 1 つ以上の名前の付いたジョブで構成され、それらのジョブの指定は jobs マップで行います。 jobs: # CircleCI上のテスト test: docker: - image: circleci/golang:latest steps: - checkout - run: echo "this is jobs" # Workflow は、ジョブの集まりとその実行順序の定義に関するルールを決めるものです。 workflows: build-test-deploy: jobs: # CircleCI上のテスト - test細かい定義は公式を確認してください。
今回はとりあえず最低限の定義のみ記載します。実運用ではCircleCI上でテストコードを実行したり、特定のブランチ(masterやstagingなど)のときにデプロイを行い自動化するようなCD「Continuous Delivery(継続的デリバリー)」を実現したりします。
上記ではworkflowsからtestというjobsが実行され、jobsのtestで指定されているdockerイメージ上でstepsに記載されているコマンドが実行されます。
CircleCIにリポジトリを登録する
まずは上記のソースコードを各自のgithub上にリポジトリを作成します。
git?github?な方はこちらや他の記事などもググって参考にしながら進めてみてください。
リポジトリが作成できたらローカルで作成したソースコードをプッシュしましょう。リポジトリの準備ができたらCircleCIに連携しましょう。
CircleCIのアカウントがない場合は https://circleci.com/ からgithubアカウントでサインアップしましょう。ログインできたらAdd Projectsを見つけて該当のリポジトリの「Set Up Projects」を実行しましょう。
既に.circleci/config.ymlを作成しているのでそのままStart Buildingしましょう
STATUSがRUNNINGからSUCCESSになれば成功です
これでプッシュされるたびにconfig.ymlに記載されている内容がcircleci上で実行されます。
フロントエンド環境を構築する
npm(Node Package Manager)でフロントエンドのパッケージを一括管理します。
npmが使えるかどうかは以下のコマンドで確認します。
npm -v && node -v
npmがない場合はまずnode.jsをインストールします。
node.jsをインストールすると一緒についてきます。
node.jsは公式から直接ダウンロードするか、homebrewを使ってダウンロードする方法があります。参考
https://qiita.com/kyosuke5_20/items/c5f68fc9d89b84c0df09初期化
プロジェクトのルートディレクトリで以下のコマンドを実行します。
npm initすると以下のようにコマンドラインでいくつか質問されると思います。
特にこだわりがなければ全てenterでいいです。package name: (beego-app) version: (1.0.0) description: entry point: (index.js) test command: git repository: (https://github.com/kqxgy385/cuddly-octo-meme.git) keywords: author: license: (ISC) Is this OK? (yes)するとpackage.jsonというファイルが生成されると思います。
npmでインストールしたパッケージのバージョン情報がpackage.jsonに格納されます。以下のような内容になっていると思います。
package.json{ "name": "beego-app", "version": "1.0.0", "description": "", "main": "index.js", "directories": { "test": "tests" }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { "type": "git", "url": "git+https://github.com/kqxgy385/cuddly-octo-meme.git" }, "author": "", "license": "ISC", "bugs": { "url": "https://github.com/kqxgy385/cuddly-octo-meme/issues" }, "homepage": "https://github.com/kqxgy385/cuddly-octo-meme#readme" }先ほどのコマンドラインで回答した内容が反映されるようになっています。
作り直したい場合は一度package.jsonを削除してもう一度npm initしてみましょう。scriptsというところでコマンドを定義しておくことが可能で、デフォルトではtestというechoしてexitするだけのスクリプトが定義されています。
ためしにnpm run test
と実行してみましょう。
今後ここにjsやcssなどのbuildのコマンドを定義して利用します。パッケージの復元
package.jsonが置いてあるプロジェクトルートで以下のコマンドを実行します。
npm installするとpackage.jsonに記載されている依存関係がインストールされ、package-lock.jsonというファイルが生成されます。
パッケージのインストール
必要パッケージのインストール方法を確認します。
以下のコマンドを実行してみます。npm install webpackするとnode_modulesというディレクトリが生成され、またpackage.jsonに以下のように追記されていると思います。
package.json"dependencies": { "webpack": "^4.41.6" }これでプロジェクト内でwebpackを利用することができます。
必要なパッケージはnpm installでインストールしていきます。
node_modulesは一般的にgit管理するものではないのでgitignoreに入れておきましょう。webpackでフロントエンドをバンドルできるようにしよう
ここから先は細かい説明は省略します。
package.jsonのscriptsとdependenciesを以下にします。package.json"scripts": { "dev": "webpack-dev-server --config webpack.config.js", "webpack": "webpack --config webpack.config.js" }, "dependencies": { "css-loader": "^3.4.0", "extract-text-webpack-plugin": "^3.0.2", "file-loader": "^5.0.2", "html-webpack-plugin": "^3.2.0", "node-sass": "^4.13.0", "sass-loader": "^8.0.0", "style-loader": "^1.1.2", "vue": "^2.6.11", "vue-loader": "^15.8.3", "vue-template-compiler": "^2.6.11", "webpack": "^4.41.5", "webpack-cli": "^3.3.10", "webpack-dev-server": "^3.10.1", "write-file-webpack-plugin": "^4.5.1" }プロジェクト直下にwebpack.config.jsというファイルを作成し、内容を以下にしてみましょう。
webpackについては公式をお勧めします。webpack.config.jsconst path = require('path'); const projectRoot = path.resolve(__dirname); const HtmlWebpackPlugin = require('html-webpack-plugin'); const VueLoaderPlugin = require('vue-loader/lib/plugin'); const WriteFileWebPackPlugin = require('write-file-webpack-plugin'); module.exports = { // エントリーポイントを指定する entry: './static/js/entry/index.js', // bundleファイルをwebpackがどこにどのような名前で出力すればいいのかを指定する output: { filename: '[name].js', path: path.join(projectRoot, 'static/js/dist') }, // webpack-dev-serverのオプションを選択する devServer: { // 使用するホストを指定する host: 'localhost', // リクエストをリッスンするポートを指定する port: '8000', // サーバーに提供するコンテンツを指定する contentBase: path.join(__dirname, "static/js/dist"), }, // @see::https://webpack.js.org/configuration/devtool/ devtool: "cheap-module-eval-source-map", // ローダーの設定 module: { rules: [ { test: /\.vue$/, loader: 'vue-loader' }, { test: /\.scss$/, use: ["style-loader", "css-loader", "sass-loader"] }, { test: /\.(png|jpg|gif)$/, use: [ { loader: 'file-loader' } ] }, ] }, // プラグインの設定 plugins: [ new VueLoaderPlugin(), new HtmlWebpackPlugin({ template: path.resolve(__dirname, 'static/index.html') }), new WriteFileWebPackPlugin(), ] };バンドルされたファイルが
static/js/dist
に生成されるように設定されています。ここまでできたらもう一度npm installします。
$ npm installここまででプロジェクトの構成は以下のようになっているかと思います。
. ├── README.md ├── conf │ └── app.conf ├── controllers │ └── default.go ├── docker │ └── Dockerfile ├── docker-compose.yml ├── main.go ├── models ├── package-lock.json ├── package.json ├── routers │ └── router.go ├── static │ ├── css │ ├── img │ └── js │ └── reload.min.js ├── tests │ └── default_test.go ├── touch └── views └── index.tplstatic直下にindex.htmlというファイルを生成し、内容を以下にしてみましょう。
<!DOCTYPE html> <html lang="js"> <head> <title>beego-app</title> </head> <body> <div id="app" v-cloak></div> </body> </html>jsというディレクトリの直下にentryというディレクトリとdistというディレクトリを作っておきます。
entryの直下にApp.vueというファイルを生成し、内容を以下にします。<template> <div id="app" class="top"> <header> <div class="title"> <a href="/">BEEGO-APP</a> </div> </header> </div> </template> <script> export default { name: "app", }; </script> <style lang="scss" scoped> .top { position: absolute; height: 1500px; width: 100%; background-color: #e8f0ff; top: 0; left: 0; header { display: flex; position: fixed; background-color: rgba(255, 255, 255, 0.9); height: 62px; width: 100%; z-index: 999; .title { display: block; position: relative; left: 10px; background-color: #d6e4ff; a { display: block; margin: 22px 0; color: #999999; cursor: pointer; text-decoration: none; &:hover { color: #da6b64; } } } } } </style>さらにentry直下にindex.jsというファイルを生成し、内容を以下にします。
index.jsimport Vue from 'vue/dist/vue.esm.js' import App from './App.vue' new Vue({ el: '#app', template: '<App/>', components: { App } });アプリケーションのデフォルトのアクセス先を変更するためrouters/router.goを以下に書き換えましょう。
router.gopackage routers import ( "github.com/astaxie/beego" ) func init() { beego.DelStaticPath("/static") beego.SetStaticPath("//", "static/js/dist") }ここまでできたらまずは以下のコマンドを実行してみましょう。
$ npm run webpack先ほどpackage.jsonのscriptsで定義したwebpackを実行するコマンドのエイリアスです。
上記コマンドを実行して http://localhost:10080/ にアクセスして以下のような画面になっていれば成功です。
うまく行ってない場合はdocker-composeの再起動なども試してみましょう。ここまでできたら次に以下のコマンドを試してみましょう。
$ npm run dev先ほどpackage.jsonのscriptsで定義したwebpack-dev-serverを実行するコマンドのエイリアスです。
先ほどのwebpack.config.jsのdevServerのportを8000番にしたことにより http://localhost:8000 にアクセスできるようになります。
http://localhost:10080/ の時と同じ画面が見れていたら成功です。今度はApp.vueの内容を適当に書き換えてみましょう。
npm run devを起動している最中はホットリロードが効いているはずなので、変更が即時で反映されていればwebpack.config.jsで設定した内容が反映されていることになります。ここまでで構成は以下のようになります。
. ├── README.md ├── beego-app ├── conf │ └── app.conf ├── controllers │ └── default.go ├── docker │ └── Dockerfile ├── docker-compose.yml ├── main.go ├── models ├── package-lock.json ├── package.json ├── routers │ └── router.go ├── static │ ├── css │ ├── img │ ├── index.html │ └── js │ ├── dist │ │ ├── index.html │ │ └── main.js │ ├── entry │ │ ├── App.vue │ │ └── index.js │ └── reload.min.js ├── tests │ └── default_test.go ├── touch ├── views │ └── index.tpl └── webpack.config.js次は?
デバッガツールdelveの導入、テストコード実行方法、MySQLコンテナを立ち上げてマイグレーションする方法などを追記します。そのうち。
参考
Getting started
https://beego.me/quickstart
Beego を触ってみる (環境構築)
https://qiita.com/macococo/items/e5ace2550418ccced9ac
Setting GOPATH
https://github.com/golang/go/wiki/SettingGOPATH
Compose ファイル・リファレンス
http://docs.docker.jp/compose/compose-file.html
npm入門
https://qiita.com/maitake9116/items/7825d90c09f3e2f87dea
webpack documentation
https://webpack.js.org/concepts/
- 投稿日:2020-02-12T02:05:53+09:00
JSON unmarshalやORMがreturnで結果を返すのではなく変数のポインタを使う理由
はじめに
GoでJSONをデコードするとき、こんな感じに変数のポインタを渡しますよね。
var result SomeStruct err := json.Unmarshal(b, &result) // ポインタを渡して結果を詰め込んでもらうrubyやpythonなら、JSONデコードってこんな感じですよね
# ruby result = JSON.parse(some_json) # 入口からJSONが入って出口からパース結果が出てくる# python result = json.load(some_json) # 入口からJSONが入って出口からパース結果が出てくるさて、なぜでしょうか。
TL;DR
動的言語なら関数がどんな型を返しても良い訳です。
resultの型が何であっても、問題なく取り扱うことができます。
が、静的言語であるGoはそのようにはいきません。
同じようにresultを受け取る方式では、関数を利用する側は
取り出したい型がわかってるのに、取り出し後に毎回型アサーションをしなければなりません。コレでピンときた人はブラウザバックです。
以上、では味気ないので、ツンと来なかった人は以下進んで下さい。
値に変更を加える、2種類の処理方法
Go言語では(他の言語でもですが…)関数やメソッドの引数に変数を渡すことも、
変数のポインタを渡すこともできます。
そして、変数を書き換える関数の実装には以下のように2通りの方法があります。package main import "fmt" // A: 変数を受け取り、returnで新しい変数を返す func plusOne(n int) int { return n + 1 } // B: 変数のポインタを受け取り、直接書き換える func plusOnePt(n *int) { *n++ } func main() { // Aの使用側 n1 := 10 n1 = plusOne(n1) fmt.Println(n1) // 11 // Bの使用側 n2 := 10 plusOnePt(&n2) fmt.Println(n2) // 11 }変数のポインタを受け取って、新しい変数のポインタを作って返却する方式もありますが、
とりあえず「とある変数を元のスコープとは別の場所で直接書き換えるかそうでないか」
という比較がしたいので、2パターンのみ記載してます。また、簡単のため以降の章でもreturnで新しい値を返す方式をA方式、
ポインタの指す値を直接書き換える方式をB方式と表現します。どちらを使うべきか
直感で何となく分かるかと思いますが、基本的にはAを使うべきです。
関数に突っ込んだ値が使用側のコードとは違う場所でいつの間にか書き換わる
というのは直感的ではないですし、出現頻度的にもAの方式のほうが「なんとなく自然」
であることはそれなりの量のコードを書いてこられた方は実感できるのではないでしょうか。パターンBが必要になる場面
さて、タイトルの回収です。
「素直に、入口に値を入れたら出口から出てきてくれればいいじゃん」
と思う人も多いかと思いますが、そうは問屋が卸さないパターンがあります。
- interface{}を返す実装を避けたいとき★本題
- 愚直な実装ではメモリを大量消費してしまうとき
- ガベージコレクションを減らしたいとき
- etc...
まさかり飛んできそうなので一応リストにはしましたが、
メモリ管理については今回の本題ではないのでそちらの話は省きます。interface{} を返す実装を避けるとき
返却値が
interface{}
になってしまう場合。
言い換えると、何を返却すればいいのか関数の実装段階で不定ななもの、ですね。例として
encoding/json
のMarshalとUnmarshal見比べてみましょう。// (前準備: 以下のような構造体があったとします。) type Message struct { Name string Body string Time int64 }まずはMarshalです。
関数シグネチャはfunc Marshal(v interface{}) ([]byte, error)
使い方は以下、returnされた値を受け取る方式(A方式)ですね。m := Message{"Alice", "Hello", 1294706395881547000} b, err := json.Marshal(m)次はUnmarshalです。
関数シグネチャはfunc Unmarshal(data []byte, v interface{}) error
使い方は以下、ポインタを渡して値を詰め込んでもらう方式(B方式)です。m = Message{ Name: "Alice", Body: "Hello", Time: 1294706395881547000, } b := []byte(`{"Name":"Bob","Food":"Pickle"}`) var m Message err := json.Unmarshal(b, &m)この2つ、同じJSONを扱う関数なのになぜ処理の仕方が違うのか?
結論から言うと、「返却したい値」が不定の場合にパターンBを使用します。
- Marshalは常に
[]byte
を返す(関数実装時に決定している)- UnmarshalはJSONの値を詰め込んだ任意のstruct (関数実装時には不定)
Marshal, Unmarshal共に、組み込み型に加えユーザーが好き勝手作った構造体を扱わなければなりません。
つまり、これらの関数が作られた段階では引数・返却値として扱うべき構造体がそもそも存在しないのです。
この未知の構造体を、Marshalは「入力」として、Unmarshalは「出力」として受け付ける
という違いがあります。仮にUnmarshalをMarshal同様にA方式で実装すると、関数シグネチャはこの様になります。
func Unmarshal(data []byte) (v interface{}, error)
さて、もしこのような実装の場合、関数利用者はどのようにJSONのUnmarshalを行うでしょうか。
m = Message{ Name: "Alice", Body: "Hello", Time: 1294706395881547000, } b := []byte(`{"Name":"Bob","Food":"Pickle"}`) maybeMessage, err := json.Unmarshal(b) // 型アサーションする if m, ok := maybeMessage.(Message); !ok { ... } ...こうなりますね。関数利用者は自分がUnmarshalしたい型はMessage型だと知っているのに、
わざわざ型アサーションでjsonのunmarshal結果がMessage型であることを示さなければなりません。
Go言語ではinterface Xを満たす型であればどのような型でもinterface Xとして扱える反面、
一度interface Xとして振る舞わせてしまったら再び元の型として扱いたくても
それを暗黙的に行うことはできません。このように、使用者側が受け取りたい型を知っているにも関わらず型アサーションの負担を強いるような場面
では、B方式を取る選択をする事が多いです。その他の例では、コードを端折ってしまいましたが僕の好きなORMである
Gormでも、やはりDBから実際に値をロードするべき入れ物が不定なため、パターンBの実装になっています。終わりに
コードを書いていて関数やメソッドが
interface{}
を返すような実装になりそうなら、
パターンBの実装を検討してみてはいかがでしょうか。ただ、ポインタによる書き換えは
いわゆる「驚き最小の原則」には従っていないと個人的には思っており、利用側が
使い方を理解できるカタチで関数コメントなりドキュメントなりに記しておく必要もあります。
最終的には「どちらの実装になっていればより利用者が楽できるだろうか」が
判断基準になってくるのかな、と思います。
- 投稿日:2020-02-12T00:59:35+09:00
【Go】メソッドの定義方法サンプルメモ
基本の形
func (<レシーバー>) <関数名>([引数]) [戻り値の型] { [関数の本体] }サンプル
sample.gopackage main import ( "fmt" ) type myInt int // 引数無し func (i myInt) plusOne() myInt { return i + 1 } // 引数あり func (i myInt) plus(j myInt) myInt { return i + j } // レシーバーの変数名省略(呼び出し元の変数にはアクセスしない) func (myInt) printHoge() { fmt.Println("Hoge") } func main() { var i myInt = 1 result := i.plusOne() fmt.Println(result) // 2 var j myInt = 4 result2 := i.plus(j) fmt.Println(result2) // 5 i.printHoge() // Hoge }参考
Go言語 - メソッド - 覚えたら書く https://blog.y-yuki.net/entry/2017/05/05/000000
改訂2版 基礎からわかる Go言語posted with amazlet at 20.02.11古川 昇
シーアンドアール研究所 (2015-07-17)
売り上げランキング: 87,346
- 投稿日:2020-02-12T00:40:51+09:00
【Go】関数の定義方法サンプルメモ
基本の形
func <関数名>([引数]) [戻り値の型] { [関数の本体] }サンプル
sample.gopackage main import ( "fmt" ) // 基本の形 func plus(a int, b int) int { return a + b } // 戻り値無し func printPlus(a int, b int) { fmt.Println(a + b) } // 戻り値&引数無し func printDummy() { fmt.Println("printDummy") } // 戻り値複数 // 元の値と、足し算した値を返す func plus2(a int, b int)(int, int, int){ return a, b, a + b } // 可変長引数 // アンダースコア変数 // https://qiita.com/penguin_dream/items/c1df36040b3fc6d42945 func printVariable(strs ...string) { for _, str := range strs { fmt.Println(str) } } // 名前付きの戻り値 func plusMinus(a int, b int)(add int, minus int){ add = a + b minus = a - b return } func main() { result := plus(1, 2) fmt.Println(result) // 3 printPlus(2, 3) // 5 printDummy() // printDummy a, b , c := plus2(1, 2) fmt.Println(a) // 1 fmt.Println(b) // 2 fmt.Println(c) // 3 printVariable("1", "2", "3") add, minus := plusMinus(2, 1) fmt.Println(add) // 3 fmt.Println(minus) // 1 }参考
Go言語 - 関数 - 覚えたら書く https://blog.y-yuki.net/entry/2017/05/03/000000
改訂2版 基礎からわかる Go言語posted with amazlet at 20.02.11古川 昇
シーアンドアール研究所 (2015-07-17)
売り上げランキング: 87,346