Go Modules時代に書籍「Goプログラミング実践入門」を実践するためのまとめ
とりあえず動かし方だけ知りたい!という方は詰まった章のヘッダに飛んで下さい!💨
動機
インプレス出版のGoプログラミング実践入門はコンセプト、内容共に、Goの基本文法を終えた学習者が次のステップアップとして取り組んでみる題材として素晴らしいものとなっています。
しかし、2017年に出版された当時、Goのバージョンは 1.7が最新でしたが、2021年現在では1.17が最新になっています。同じメジャーバージョン1なので、Goの素晴らしい互換性により1.17のGoでも書籍に掲載されているソースコードは正しいものとなっています。
ですが、書籍の通り進めていってすぐに問題に突き当たります。ソースコードのビルドができない・・・
このことは、正誤表にも少し書いてあるのですが、本全体として2021年現在のGo環境で実践するには補足しておいた方が良い事柄がいくつかあったので、まとめておくことにしました。
Go初学者の人が、本を手に取って、本の通りに動かしてみたら変なエラーが出て動かなかった。それを理由にこの本をやめてしまわないで済む人が増えれば幸いです。
Special Thanks
この記事で紹介している補足は、私がGoプログラミング実践入門のもくもく会を実施したときに参加者の方々が気づいたものです。問題を共有してくれた参加者の皆様、ありがとうございます。
この記事に書いてあること
書籍の内容を2021年現在Goの最新バージョンである1.17で実践するときに、書籍の通りにしては上手くいかないところに焦点を当てます。
動かないところに対し以下を提示します。
- 動かない原因
- 動かすために必要な新機能等の知識
- 動かすための修正例
新機能等の知識については簡単に説明
第1章
1.10 Hello Go
go install
コマンドでビルドするように指示がありますが、いきなり動きません😱
最初のHello Worldでいきなり動かないと悲しいですよね😢
動かないのはGo1.11から試験的に導入された Go Modules が長い検証、移行期間を経て、Go1.16からデフォルトでONになったためです。
この本でGoのソースコードをビルドするときに動かないのは、基本的にGo modulesに移行したことによるものなのでもう少し補足しておきます。
go install [package path]
はpackage pathのpackageのソースコードを取得し、go build
実行, 出力バイナリを$GOPATH/bin
に配置するという動きをします。
書籍で指定する first_webapp
はドメインが含まれていないので、PC内(具体的には$GOROOT
, $GOPATH
)にそのpackageが存在しているか検索しますが、無いので cannot find package "first_webapp"
というエラーが出ます。
この対処法は2通りあって、書籍全般を通して同じ対処法が適用できます。
- Go modulesをoffにする (あまりお勧めしません)
- module mode で動くように変更を加える
現在のGoはGo modulesを使用していくのが標準になので、module modeで書いていくための練習ということでmodule mode対応していくのがお勧めです。
やり方は以下のようにすればいいのですが、詳細については公式のこちらをぜひ一読して下さい。
go mod init first_webapp
go build
first_webapp
ちなみに、 Go modulesをoffにする方法は、 環境変数GO111MODULE
にoff
を渡してやればよく、対象のpackage path は current directoryを指定します。
$ GO111MODULE=off go install .
$ first_webapp
第2章
第2章の内容は、後続の章で説明している内容を先取りして紹介しているので、第9章まで一通り終えてから改めて戻ってくるとより理解が深まるような内容になってます。
2.6 PostgreSQL のインストール
2021年では、PostgreSQL version 13 が出ているので、書籍の記載通りではなく、ご自身でインストールしたバージョンに合わせて下さい。
PostgreSQL の if exists
に関しての文法が変わっているので、そこだけSQLの書き換えが必要なことに注意すれば最新の13をインストールして進めても問題にはなりません。
2.8 サーバの起動
go build
をするように指示がありますが、書籍通りのコマンドでは動きません。
1.10 Hello Go
が動かないのと原因は同じなので module modeの場合、対応はほとんど同じです。
Go modulesをoffにして動かしたい場合は一つ手順を加える必要があります。
このサンプルは サードパーティのpackage github.com/lib/pq
を参照しているので、これをgo get
してソースコードをダウンロードしておく必要があります。
GO111MODULE=off go get github.com/lib/pq
GO111MODULE=off go build
書籍通りのPostgreSQLのバージョンをセットアップしていない場合、データベースに繋がらないようなエラーが出てサンプルが動かない可能性があります。
ここでデータベース接続情報を与えているのですが、ユーザー・パスワードはデフォルトの値を使うようになっていますが、デフォルトの想定が異なる場合は繋がりません。
例えば以下のように明示してあげれば繋がります。
Db, err = sql.Open("postgres", "user=root password=root dbname=chitchat sslmode=disable")
第3章
3.4 HTTP/2 の使用
手元のcurlによっては、--http2
に対応していない場合があるようです。
nghttp2
の表示があれば問題ないようです。
$ curl --version
curl 7.64.1 (x86_64-apple-darwin20.0) libcurl/7.64.1 (SecureTransport) LibreSSL/2.8.3 zlib/1.2.11 nghttp2/1.41.0
第4章
4.2.3 MultipartForm
動作しないということはないのですが、multipart/form-data
を指定したフォームでの実行結果が書籍と一致しません。
これはこのissue でPostFormValue()
の動作が修正されているためです。
第6章
6.4.1 データベースの準備
用意したPostgreSQLのバージョンによって、drop table ~ if exists
が動作しないことがあります。
用意したPostgreSQLのバージョンのドキュメントを参照して、 drop tableの書き方を調べてみて下さい。(PostgreSQL 13の場合はこんな感じで、書籍と違うので修正が必要です)
drop table posts ccascade if exists;
drop table comments if exists;
6.5.1 Sqlx
リスト6.17 で以下の部分のコードはstruct tagの書き方が間違っています。
AuthorName string `db: author`
また、 type Post struct
のコードが2回登場するので、間違っている1回目のものは無視すると良いと思います。
第10章
10.2 Herokuへのデプロイ
godep
という依存ファイル管理ツールを使う手順が紹介されていますが、プロジェクトはアーカイブされており、READMEでは dep
を使うように促していますが、dep
も同じくアーカイブされています。
Go modulesを使いましょう。
go mod init ws-h
go mod tidy
また、書籍に記載されている手順の中ではPostgreSQLについての説明が省かれているので、herokuにWebサーバーをデプロイ成功しても残念ながら動きません。
サンプルコードでは、elephantsql.com でPostgreSQLを用意して、そこに繋がるようになっています。このサーバーは2021年09月現在でも動作しているので、このサンプルコードの接続情報を使えば繋ぐことができます。
(繋いで見ればわかりますが、数人が同じように試しているので、いくつかのデータが既に登録済みになっています)
elephantsql.com はFREEプランも用意しているので、そこでサンプルと同じように自身でDBを新しく作るのが一番手っ取り早いと思います。
HerokuのPostgresサービスを使って動かす
また、Herokuも制限付きながら無料でPostgreSQLデータベースを提供しているので、そちらで動かしたい方向けに簡単に手順を記載しておきます。
Heroku上の情報の取得にコマンドを使用していますが、HerokuのWeb画面でも同じ情報を確認することができます。
こちらのDocs通り、ソースコードのDB接続設定を環境変数 DATABASE_URL
から取得するように変更します。
Db, err = sql.Open("postgres", os.Getenv("DATABASE_URL"))
PostgreSQLサーバーを作成して、作成したDBの情報を取得します。 ws-h-xxxxxx のところは heroku create
を実行したときに指定したapp nameにして下さい。
heroku addons:create heroku-postgresql:hobby-dev
heroku pg --app ws-h-xxxxxx
Add-on: postgresql-tapered-12345
のような値がdatabase nameです。
PostgreSQLにpsql
を使用して、CREATE TABLE
が記載された setup.sqlファイルを実行します。(psqlコマンドを使うので、インストールされている必要があります)
heroku pg:psql postgresql-tapered-12345 --app ws-h-xxxxxx < ./setup.sql
Heroku AppのWeb URLを確認することで、curl
で動作確認することが出来ます。
heroku info ws-h-xxxxxx
10.3 Google App Engineへのデプロイ
補足
情報が正確ではない可能性がありますが、調べてみると書籍が執筆された当時は、Google App Engineから外に通信をすることは課金対象だったためか、Socket APIを使う必要があったようです。
Cloud SQLはその例外だったため、手順としてCloud SQLでMySQLを起動して接続するという方法となっているようです。
現在はApp Engine Standardのランタイム説明によると外部へはフルアクセスとなっているようです。
また、Cloud SQLもMySQLだけでなく、PostgreSQLもサポートするようになっているので、PostgreSQLを指定して作成するようにすれば、ソースコード上のクエリのプレースホルダを?
から $1
などに変更する必要もなくなっています。
Google Cloud SDKのインストール
Google Cloudへのログインが必要になります。あらかじめアカウントを作成しておいて下さい。
gcloud init
gcloud components install app-engine-go
プロジェクトの準備
ProjectIDはユニークである必要があります。なので、ここからの手順で登場するxxxxx
は全て任意の文字列で読み替えて下さい。また、指定しなくてもいいオプションがいくつかありますが、なるべく料金がかからない設定を明示的に指定しています。
gcloud projects create gowebprog-xxxxx
標準のprojectを変更しておきます。
gcloud config set core/project gowebprog-xxxxx
GAEへアップロードするソースコードのアップロード先bucketを明示的に作成します。
(us-east1 は free-tier 対象)
gsutil mb -b on -l us-east1 gs://gowebprog-xxxxx/
Cloud SQLの準備
Cloud SQLは elephantsql.com のFREEプランに比べると、それなりにちゃんとした料金がかかるため、気になる人はCloud SQLを準備せずにDBの接続情報を elephantsql.com に設定して下さい。
sql API を有効にします。
gcloud services enable sql-component.googleapis.com sqladmin.googleapis.com
free-tier は無いのでPostgreSQL最小の db-f1-micro
を指定して作成します。
gcloud sql instances create gowebprog-db-xxxxx --database-version=POSTGRES_13 --tier=db-f1-micro --region=us-east1 --availability-type=zonal
STATUSを確認します。
gcloud sql instances list
root:password のユーザーを作成します。
gcloud sql users create root --password=password --instance=gowebprog-db-xxxxx
gcloud sql databases create gowebprog --instance=gowebprog-db-xxxxx
gcloud sql connect gowebprog-db-xxxxx --user=root --database=gowebprog
PostgreSQLに接続します。
$ gcloud sql connect gowebprog-db-xxxxx --user=root --database=gowebprog
Allowlisting your IP for incoming connection for 5 minutes...done.
Connecting to database with SQL user [root].Password:
psql (13.4, server 13.3)
SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off)
Type "help" for help.
gowebprog=>
gowebprog=>
のプロンプトが出てきたら、テーブル作成するSQLを実行します。
create table posts (
id serial primary key,
content text,
author varchar(255)
);
テーブルが作成出来たことを確認します。
gowebprog=> \dt
GoのソースコードをGoogle App Engine用に修正
Goのソースコードの修正は、こちらのCloud SQLを使用するサンプルを参考にすると良さそうです。
cloudsqlのサンプルコードを頼りにapp.yamlを作成します。
runtime: go116
env_variables:
# Replace INSTANCE_CONNECTION_NAME with the value obtained when configuring your
# Cloud SQL instance, available from the Google Cloud Console or from the Cloud SDK.
# For Cloud SQL 2nd generation instances, this should be in the form of "project:region:instance".
CLOUDSQL_CONNECTION_NAME: INSTANCE_CONNECTION_NAME
# Replace username and password if you aren't using the root user.
CLOUDSQL_USER: root
CLOUDSQL_PASSWORD: password
INSTANCE_CONNECTION_NAMEの部分はDBへの接続名を取得して確認します。
gcloud sql instances describe gowebprog-db-xxxxx | grep connection
サンプルにはCloud SQL PostgreSQLに接続するための設定を環境変数から読み出しているコードもあるのでそのまま使わせてもらいましょう。
var (
connectionName = mustGetenv("CLOUDSQL_CONNECTION_NAME")
user = mustGetenv("CLOUDSQL_USER")
dbName = os.Getenv("CLOUDSQL_DATABASE_NAME") // NOTE: dbName may be empty
password = os.Getenv("CLOUDSQL_PASSWORD") // NOTE: password may be empty
socket = os.Getenv("CLOUDSQL_SOCKET_PREFIX")
)
// /cloudsql is used on App Engine.
if socket == "" {
socket = "/cloudsql"
}
// connection string format: user=USER password=PASSWORD host=/cloudsql/PROJECT_ID:REGION_ID:INSTANCE_ID/[ dbname=DB_NAME]
dbURI := fmt.Sprintf("user=%s password=%s host=/cloudsql/%s dbname=%s", user, password, connectionName, dbName)
conn, err := sql.Open("postgres", dbURI)
App Engineを準備
App Engineを作成します。
gcloud app create --region=us-east1
コードをデプロイします。
$ gcloud app deploy --project=gowebprog-xxxxx --bucket=gs://gowebprog-xxxxx/
デプロイしたAppにアクセスするためのURLは、デプロイ時のlogに target url: [https://gowebprog-xxxxx.ue.r.appspot.com]
のような形で出力されています。
片付け前のバックアップ
この手順は、PostgreSQLのデータを残しておきたい場合に必要となる手順なので、興味のある人だけやってみて下さい。
Cloud SQL PostgreSQLのデータExport手順
普通にexportコマンドを実行したいところですが、前準備としてCloud SQL instance から bucketへのpermissionを与える必要があります。
Cloud SQL instance の service account email 情報を取得します。
export serviceAccountEmail=$(gcloud sql instances describe gowebprog-db-xxxxx | grep service | cut -d " " -f 2)
echo $serviceAccountEmail
Bucket Object Creator roleを Cloud SQLに与えます。
gsutil iam ch serviceAccount:$serviceAccountEmail:roles/storage.objectCreator gs://gowebprog-xxxxx
他に与えれる権限はこちらを参照して下さい。
role割り当てを確認します。
gsutil iam get gs://gowebprog-xxxxx
exportを実行(バックアップファイル名は重複NGなので、日時をsufixにしています)
gcloud sql export sql gowebprog-db-xxxxx gs://gowebprog-xxxxx/sql-backup-$(date +%y%m%d%H%M).sql --database gowebprog
出力されたファイルを確認します。
gsutil ls gs://gowebprog-xxxxx
ファイルをダウンロード。
gsutil cp gs://gowebprog-xxxxx/sql-backup-2109211907.sql .
gsutil のコマンド一覧はこちらを参考にして下さい。
Cloud SQLを一時停止する
一旦Cloud SQLを停止しておいて、後で再度使う場合に課金を抑える手順です。
Cloud SQLを一時停止と再開手順
Cloud SQLを停止してもIPアドレスとstorage分は微量ですが課金されます。
gcloud sql instances patch gowebprog-db-xxxxx --activation-policy=NEVER
gcloud sql instances describe gowebprog-db-xxxxx | grep state
一時停止しているだけなので、再起動する場合は以下を実行します。
gcloud sql instances patch gowebprog-db-xxxxx --activation-policy=ALWAYS
gcloud sql instances restart gowebprog-db-xxxxx
後片付け(プロジェクトの削除)
Cloud SQLを削除します。
gcloud sql instances delete gowebprog-db-xxxxx
App Engine instanceは、一定時間アクセスがなければ自動的に削除される仕組みになっており、手動で削除することは想定されていないようです。
SERVICE
, VERSION
, ID
の値を使って現在起動中のインスタンスを削除することは出来ますが、あえて消す必要はありません。
gcloud app instances list -s default --project gowebprog-xxxxx
gcloud app instances delete $INSTANCE_ID -s default -v $VERSION
bucketを削除します。
gsutil rm "gs://gowebprog-xxxxx/*"
gsutil rb gs://gowebprog-xxxxx
projectを削除します。
gcloud projects delete gowebprog-xxxxx
間違えて消した場合は、一定期間の間(多分30日)は完全に消えずに復活できます。
gcloud projects undelete gowebprog-xxxxx
10.4 Dockerへのデプロイ
2021年現在では、HerokuがDockerコンテナの実行に対応しています。
なので、Digital OceanよりはHerokuの方が簡単に試せると思います。
10.4.4 Go WebアプリケーションのDocker対応
コードの修正
書籍のDockerfileのままdocker build
を実行すると、次のようなエラーが出てしまうと思います。
# > [5/5] RUN go install github.com/sausheong/ws-d:
# ##10 0.231 go install: version is required when current directory is not in a module
# ##10 0.231 Try 'go install github.com/sausheong/ws-d@latest' to install the latest version
これは、Dockerfile冒頭で FROM golang
の部分でtagを指定していないためにFROM golang:latest
、 つまり書籍執筆時とは異なる最新のGoのイメージを使うことになってしまっているためです。
go install
の仕様がGo1.16から変更され、module modeではない場合はパッケージ versionを github.com/sausheong/ws-d@x.y.z
のような形で指定する必要があるためです。
かといって、Go versionを書籍の付録に記載のversionと同じFROM golang:1.7.4
にすると、今度は github.com/lib/pq
がGo1.13以上を想定しているために動きません。
ということで、DockerfileのFROM
は以下のようにすると動きます。
FROM golang:1.13
また、Herokuの場合、環境変数 PORT
でWebサーバーの接続を受け付ける必要があるので、server.go
で$PORT
を使うようにしておきます。
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
server := http.Server{
Addr: fmt.Sprintf(":%v",port),
}
HerokuへのDockerデプロイ
herokuコマンドで次のようにするとデプロイできます。
heroku create ws-d-xxxxxxx
heroku container:login
heroku container:push --app ws-d-xxxxxxx web
heroku container:release --app ws-d-xxxxxxx web
PostgreSQLについては 10.2 Herokuへのデプロイ と同じなので省略します。
Discussion