【Go言語(golang)入門】golangに入門してみた話
Goがいけてるらしい
結構前からずっと「golangがいけてる👍」ってよく聞きます。(よね?)
でも、これまでgolangって触ったことがありませんでした。
だから、golangで簡単なAPIサーバを作って、どんな感じの言語なのか感じておこうと思います!
Go言語(golang)
言語仕様
golangの特徴として以下のようなことが、よく挙げられていました。
- 速い
- コンパイル型言語
- 平行処理のサポートが良い
- シンプルな記述
- 高い安全性
golang における Web Application Framework(WAF)
golangにおけるWAFは基本的にマイクロフレームワークのようです。
もちろん、フルスタックフレームワークも存在します。
代表的なマイクロフレームワーク
代表的なフルスタックフレームワーク
いろいろありますが、
こちらのサイトを参考にすると、
golang標準ライブラリの net/http
互換であるものがいいらしい!
ということで、
上記参考サイトでおすすめされているものの中で最もGitHubのスター数が多かった
mux を使ってみることにしました。
今回作るもの
mux を使って、TODOを管理するAPIを作っていきます。
完成版は下記リポジトリにあります。
golang + mux でTODO管理API作成
動作環境
いざ、実装
1. main.go
を作成
まずはじめに、muxではルーティングおよびハンドラ(処理)を
どのように設定するのか確認するために
1ファイルで簡単なAPIを実装してみます。
// Filename: main.go package main import ( "fmt" "log" "net/http" "github.com/gorilla/mux" ) func main() { const Host = "localhost:5000" // Go的慣例:定数には型を指定しない router := mux.NewRouter() router.HandleFunc("/users/{id:[0-9]+}", usersHandler).Methods("GET") fmt.Println("Server Start >> " + Host) log.Fatal(http.ListenAndServe(Host, router)) } func usersHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) w.WriteHeader(http.StatusOK) fmt.Fprintf(w, "User No.%v", vars["id"]) }
上記コードについて説明します。
main関数
- ルーティング設定
/users/{id:[0-9]+}
はパスパラメータで指定したユーザの情報を取得するためのパスを設定しています。
/users/{id:[0-9]+}
のパスに来たリクエストを処理するのは usersHandler
です。
(usersHandler関数については後述)
- サーバ設定
今回は localhost:5000
でリクエストを受け付けるようにします。
usersHandler関数
本関数は先程述べたとおり
/users/{id:[0-9]+}
のパスに来たリクエストに対する処理内容を記述します。
ビジネスロジックとかって言い方しますよね。
2. 起動
$ go run main.go
上記コマンドでサーバを起動します。
実際に実行してみると
$ go run main.go Server Start >> localhost:5000
このように表示されるでしょう。
では、実際にリクエストを投げてみます。
3. テスト
Postmanでもなんでもいいので
GETメソッドで http://localhost:5000/users/1
にリクエストを投げます。
すると
User No.1
というレスポンスが返って来たと思います!
http://localhost:5000/users/2
にリクエストを投げれば
User No.2
になりますよね!
4. より実践的なプログラムに変更
僕的、実践的プログラムとは、
- ハンドラごとにファイルを分離
- DBからデータを取得
上記のような処理を内包するプログラムです。
では、まずファイルの分離からやっていきましょう。
ディレクトリ構成
├── handler │ └── users_handler.go └── main.go
ルーティングとハンドラ(ビジネスロジック)を分けてみます。
コードの中身
// Filename: main.go package main import ( "fmt" "log" "net/http" "github.com/gorilla/mux" "api-server-tutorial/handler" // ★ここ!handlerディレクトリ配下をインポート ) func main() { const Host = "localhost:5000" router := mux.NewRouter() router.HandleFunc("/users/{id:[0-9]+}", handler.UsersHandler).Methods("GET") fmt.Println("Server Start >> " + Host) log.Fatal(http.ListenAndServe(Host, router)) }
// Filename: handler/users_handler.go package handler import ( "fmt" "github.com/gorilla/mux" "net/http" ) func UsersHandler(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) w.WriteHeader(http.StatusOK) fmt.Fprintf(w, "User No.%v", vars["id"]) }
main.go
にて、
api-server-tutorial/handler
配下のハンドラをインポートします。
これでhandler
ディレクトリ配下に配置したハンドラを利用できるようになります。
各自レスポンスが正しく返ってくるか試してみてください。
5. DBからデータを取得
Webサービスを作成するにあたってDBは必須ですよね!
今回は、GoでDB操作するさいによく使われているらしい
Gormを使ってみます。
GormとMySQL用パッケージのインストール
$ go get -u github.com/jinzhu/gorm $ go get -u github.com/go-sql-driver/mysql
-u
オプションは最新バージョンをインストールする
っていう意味です。
今回はMySQLを使用します。
テーブル作成
テスト用にUsersテーブルを作成します。
CREATE TABLE users ( id INT AUTO_INCREMENT NOT NULL PRIMARY KEY, name VARCHAR(255) NOT NULL, age INT NOT NULL, created_at DATETIME, updated_at DATETIME );
mysql> describe users; +------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +------------+--------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | name | varchar(255) | NO | | NULL | | | age | int(11) | NO | | NULL | | | created_at | datetime | YES | | NULL | | | updated_at | datetime | YES | | NULL | | +------------+--------------+------+-----+---------+----------------+
DB操作機能を追加
package main import ( "api-server-tutorial/handler" "fmt" "github.com/gorilla/mux" _ "github.com/jinzhu/gorm/dialects/mysql" "log" "net/http" ) func main() { const Host = "localhost:5000" router := mux.NewRouter() router.HandleFunc("/users/{id:[0-9]+}", handler.UsersShowHandler).Methods("GET") // 【変更箇所】 router.HandleFunc("/users", handler.UsersCreateHandler).Methods("POST") // 【変更箇所】 fmt.Println("Server Start >> " + Host) log.Fatal(http.ListenAndServe(Host, router)) }
今回はDBからIDを指定してユーザを取得するAPIと
名前と年齢を指定して新しいユーザを作成するAPIを作成します。
したがって、 main.go
は ルーティング部分(【変更箇所】)が変わっています。
また、各エンドポイントごとにハンドラを設定しています。
// Filename: handler/users_handler.go package handler import ( "encoding/json" "fmt" "github.com/gorilla/mux" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" "net/http" "time" ) // Model共通分を表す構造体 type Model struct { ID uint `gorm:"primary_key" json:"id"` CreatedAt time.Time UpdatedAt time.Time } // Userを表す構造体 type User struct { Model Name string Age int } // User作成用のパラメータを表す構造体 type UserParams struct { Name string Age int } func UsersShowHandler(w http.ResponseWriter, r *http.Request) { // DBに接続 db := gormConnect() defer db.Close() // dbを使い終わったらクローズ vars := mux.Vars(r) // 指定IDのユーザを取得 var user User db.First(&user, vars["id"]) w.WriteHeader(http.StatusOK) fmt.Fprintf(w, "Show this user -> %v", user.Name) } func UsersCreateHandler(w http.ResponseWriter, r *http.Request) { // DBに接続 db := gormConnect() defer db.Close() // dbを使い終わったらクローズ // リクエストBodyをJSONにパース decoder := json.NewDecoder(r.Body) var userParams UserParams error := decoder.Decode(&userParams) // userParamsとbody内の対応するキーの値を入れてくれる if error != nil { w.Write([]byte("json decode error" + error.Error() + "\n")) } defer r.Body.Close() // bodyを使い終わったらクローズ //INSERT実行部分 var user User user.Name = userParams.Name user.Age = userParams.Age db.Create(&user) w.WriteHeader(http.StatusOK) fmt.Fprintf(w, "Create this user -> %v", user.Name) } func gormConnect() *gorm.DB { DBMS := "mysql" USER := "root" PASS := "mysql" PROTOCOL := "tcp(127.0.0.1:3306)" DBNAME := "go_api_db" db,err := gorm.Open(DBMS, USER+":"+PASS+"@"+PROTOCOL+"/"+DBNAME) if err != nil { panic(err.Error()) } return db }
(<>
部分は各自変更してください)
handler/users_handler.go
の方は
UsersShowHandler()
:指定IDのユーザを取得UsersCreateHandler()
:名前と年齢を指定してユーザを作成
上記2つのハンドラを作成ました。
URLの中からパラメータ (リクエストクエリ)を取得する方法と
リクエストBodyからパラメータを取得する 方法が異なるので注意が必要です。
defer
というのを使っていますが、
これは 特定の処理を関数の一番最後に実行する という機能を持っています。
したがって、今回の場合だと db
を使い終わったらコネクションを破棄することができます。
本来であればDB設定は別ファイルに切り出すべきでしょうが、今回は簡略化のためにusers_handler.go
の中に記述します。
6. テスト
新規ユーザの作成
ユーザがいなければ取得もできないので、まずはユーザを作成してみましょう。
http://localhost:5000/users
に POST
リクエストを投げてみてください。
なお、 Bodyは以下のようにします。
{ "name": "hogehoge", "age": 20 }
リクエストを投げると…
Create this user -> hogehoge
このようなレスポンスがきましたね!
では、DBを見てみましょう。
SELECT * FROM users WHERE id = 1;
結果は
+----+----------+-----+---------------------+---------------------+ | id | name | age | created_at | updated_at | +----+----------+-----+---------------------+---------------------+ | 1 | hogehoge | 20 | 2019-02-07 16:02:17 | 2019-02-07 16:02:17 | +----+----------+-----+---------------------+---------------------+
作成できていますね!
指定ユーザの取得
先程作成したユーザを取得してみましょう。
ID は 1 でしたね。
したがって、以下のエンドポイントに対して GET
リクエストを送信します。
http://localhost:5000/users/1
結果は
Show this user -> hogehoge
はい、指定ユーザの名前を取得できました!
唐突のまとめ
普段僕は Spring Boot や Rails, FuelPHP など、いわゆるフルスタックフレームワークによる開発をメインで行っています。
なので、今回初めてマイクロフレームワークを使うと、不便だと感じることが多々ありました。
でも、フルスタックフレームワークだと、細かいところに手が届かないときがあるので、そういう点ではマイクロフレームワークの方がいいなと思います。小回りが効きますね!
(コード書くのが大好きな僕からすると、いろいろ処理書けるので、ただただおもしろい!)
マイクロサービス主流のこのご時世、マイクロフレームワークという選択肢を持っておくことは必須ですよね。
是非ともマスターしたい技術です!
P.S.
ところどころ手抜きがある & 僕自身golang初心者なので、「golang + mux ってこんな感じなんだ」 程度に見てくださいね笑