Go言語でデータベースを使う処理を書いてHeroku上に公開してみる手順の覚書
はじめに
Go言語でWebアプリを作ってみたいと思い,作ったWebアプリを簡単に公開できる方法について調べていた.まずは無料でできる方法から始めたかったので,そこでHerokuを使ってみることにした.
また,アプリを作る上でデータベースに関わる処理も必要になってくるので,データベースが関わるコードをHerokuで動作させるために必要な設定の仕方についても試してみることにした.
今回試すのは,取りあえずデータベースに適当なテーブルを作成し,そのデータを読み取ってHTMLに表示するだけのGo言語で書いたサーバーサイドプログラムである.Heroku上でデータベース連携の処理を実行するのは,アドオンを追加したり等ローカルで試すのとは勝手が異なるので,少しつまづきそうになった.手順を覚書として書いておく.
次節に手順を書く.
フォルダ構成
[~/Code/Project/proj_y2022/proj0102/jikken3] % tree . . ├── dbdata │ └── setup.sql └── main.go 1 directory, 2 files
Heroku上で実行されるサーバサイドのプログラム
main.go
今回注目するのはhellodb関数であり,これはURLで/db
のパスにアクセスしたときの処理について書いてある.ここで行なっているのは,テーブルから全てのレコードを読み取り,カラム(name)を改行で連結した文字列として出力する.
package main import ( "io" "net/http" "os" "log" "database/sql" _ "github.com/lib/pq" "strings" ) var Db *sql.DB func hello(w http.ResponseWriter, r *http.Request) { io.WriteString(w, "Hello World!!") } type member struct { NUM string NAME string } func hellodb(w http.ResponseWriter, r *http.Request) { Db, err := sql.Open("postgres", os.Getenv("DATABASE_URL")) defer Db.Close() if err != nil { log.Fatalf("Error opening database: %q", err) } rows, err := Db.Query("SELECT * FROM members") defer rows.Close() if err != nil { io.WriteString(w, "Errorrr!") return } var out string var names []string for rows.Next() { var e member rows.Scan(&e.NUM, &e.NAME) names = append(names, e.NAME) } out = strings.Join(names, "\n") io.WriteString(w, out) } func main() { port := os.Getenv("PORT") http.HandleFunc("/", hello) http.HandleFunc("/db", hellodb) http.ListenAndServe(":"+port, nil) }
setup.sql
上記のGo言語のプログラム(main.go)からアクセスされることになる,今回のサンプルとしてのテーブルを定義する.テーブル(members)は名前(name)と番号(id)をレコードとして保存する構成であり,2つのカラム(id, name)を持つ.
[~/Code/Project/proj_y2022/proj0102/jikken3] % cat dbdata/setup.sql CREATE TABLE members ( id integer, name varchar(30) ); INSERT INTO members VALUES (1, 'John Smith'); INSERT INTO members VALUES (2, 'Richard Roe'); INSERT INTO members VALUES (3, 'Baby Doe');
Herokuにデプロイするまでの流れ
流れとしては以下である.詳細は次になる.
- (1) リポジトリを作る
- (2) Heroku上にアプリを作成する
- (3) Herou上のアプリにデータベースを追加する
- (4) 作成したコードをHeroku上のリポジトリにpushする
- (5) Heroku上のアプリのデータベースにテーブルを作成する
(1) リポジトリを作る
[~/Code/Project/proj_y2022/proj0102/jikken3] % git init
同じディレクトリ内で以下を実行する.go mod init
で作成されるgo.mod
ファイルはGoモジュールの依存関係を記述したファイルであり,これはHeroku上でのビルドにおいて使用される.
% go mod init github.com/<ユーザ名>/jikken3 % go mod tidy % go mod vendor
(2) Heroku上にアプリを作成する
heroku create
コマンドでアプリの名前を指定できる.(とりあえずjohnapp47
としておく)
[~/Code/Project/proj_y2022/proj0102/jikken3] % heroku create johnapp47
(3) Herou上のアプリにデータベースを追加する
今回のコードはpostgresqlを使うので,以下のようにしてHerokuアプリにアドオンを追加する.
% heroku addons:create heroku-postgresql:hobby-dev --app johnapp47
(4) 作成したコードをHeroku上のリポジトリにpushする
[~/Code/Project/proj_y2022/proj0102/jikken3] % git add -A . % git commit -m "test commit" % git push heroku master
(5) Heroku上のアプリのデータベースにテーブルを作成する
Herokuアプリ(今回はjohnapp47)の上に展開された,Go言語のサーバーサイドプログラムから利用するデータベースに対して,先に挙げたsetup.sql
のSQLを実行し,テーブルを作成する.
まず確認として,ファイル群は先ほどpushしたので,setup.sql
は以下のようにして認識されている.
[~/Code/Project/proj_y2022/proj0102/jikken3] % heroku run bash --app johnapp47 Running bash on ⬢ johnapp47... up, run.8167 (Free) ~ $ ls Procfile bin dbdata go.mod go.sum main.go vendor ~ $ ls dbdata setup.sql ~ $
それでは,テーブルを作成するためにsetup.sql
の中身のSQL文をデータベース上で実行する.
HerokuのPostgresサーバにのコンソールにアクセスするにはheroku pg:psql --app <アプリ名>
と実行すればよい.
[~/Code/Project/proj_y2022/proj0102/jikken3] % heroku pg:psql --app johnapp47 --> Connecting to postgresql-convex-34468 psql (14.1, server 13.5 (Ubuntu 13.5-2.pgdg20.04+1)) SSL connection (protocol: TLSv1.3, cipher: TLS_AES_256_GCM_SHA384, bits: 256, compression: off) Type "help" for help. johnapp47::DATABASE=>
ファイル(setup.sql)を読み込んで実行するコマンドは\i <ファイル名>
johnapp47::DATABASE=> \i dbdata/setup.sql CREATE TABLE INSERT 0 1 INSERT 0 1 INSERT 0 1 johnapp47::DATABASE=> \dt List of relations Schema | Name | Type | Owner --------+---------+-------+---------------- public | members | table | jutsizbfgorver (1 row)
テーブルが作成されたのをselect文で確認.
johnapp47::DATABASE=> select * from members; id | name ----+------------- 1 | John Smith 2 | Richard Roe 3 | Baby Doe (3 rows) johnapp47::DATABASE=> \q
デプロイされたアプリにアクセスしてみる
https://johnapp47.herokuapp.com/db
にアクセスすると,以下のような表示を確認.
Go言語で書いたサーバプログラムがデータベースからレコードを読み込んでテーブル(members)から読んだ名前の一覧を表示できたことを確認できた.
おわりに
Heroku上でデータベースが連携するコードをデプロイする方法について理解できた.個人的にDockerを学び始めたこともあり,この後にDocker on Herokuなる存在を知ったので次はこっちも試してみようと思う.
参考資料
Docker(docker-compose)でPostgreSQLの環境構築の覚書
はじめに
Dockerを用いたPostgreSQLの環境構築について試したときのメモを書いておく.
以下を参考にした
やったこと
DockerでPostgreSQLサーバをコンテナとして起動して,クライアントプログラム(psql)からデータベースにアクセスしたりをやってみた.また,イメージの初回起動時に事前に.sqlファイルに定義したテーブルが作成されるようにしたりした.
環境構築の前に
以下を実行した.postgresのベースとなるイメージの取得と,PostgreSQLのクライアントの方のインストールを行う.
% docker pull postgres:14-alpine
クライアントが欲しい場合はlibpqをinstallする.
% brew install libpq
フォルダ構成
以下のような構成から成る.
[~/Code/Project/proj_y2021/proj1229/docker-postgres] % tree -a . . ├── docker-compose.yml └── postgres ├── .env ├── Dockerfile └── db └── intidb.sql 2 directories, 4 files
各ファイルについて
docker-compose.yml
今回はローカルマシンの5433ポートからコンテナ内のpostgresサーバ(5432ポートで待機)にアクセスすることにする.
また,ユーザ情報等の環境変数の内容は外部のファイル".env"に分離したので,長くならずすっきりしたと思う.
また,イメージの初回起動時に実行される.sqlファイルを格納したフォルダはdocker-entrypoint-initdb.d
にマウントされるように設定した.
[~/Code/Project/proj_y2021/proj1229/docker-postgres] % cat docker-compose.yml version: '3' services: db: container_name: test-postgres-db build: ./postgres/ env_file: ./postgres/.env volumes: - ./postgres/db:/docker-entrypoint-initdb.d ports: - "5433:5432"
Dockerfile
[~/Code/Project/proj_y2021/proj1229/docker-postgres] % cat postgres/Dockerfile FROM postgres:14-alpine ENV LANG ja_JP.utf8
.env (ユーザ情報を定義した環境変数をまとめたファイル)
[~/Code/Project/proj_y2021/proj1229/docker-postgres] % cat postgres/.env POSTGRES_USER=postgres POSTGRES_PASSWORD=hogehogepw POSTGRES_DB=test_db
init.sql
コンテナ起動時に,データベースにテーブルが作成するようにする.以下の仮のテーブル(名前を保存する"members")を作ってみる.
[~/Code/Project/proj_y2021/proj1229/docker-postgres] % cat postgres/db/intidb.sql CREATE TABLE members ( id integer, name varchar(30) ); INSERT INTO members VALUES (1, 'John Smith'); INSERT INTO members VALUES (2, 'Richard Roe');
membersと名付けたテーブルに適当なエントリを挿入した.
イメージのビルド
[~/Code/Project/proj_y2021/proj1229/docker-postgres] % docker-compose build
コンテナの起動
[~/Code/Project/proj_y2021/proj1229/docker-postgres] % docker-compose up -d Creating network "docker-postgres_default" with the default driver Creating test-postgres-db ... done
クライアント(psql)でデータベースにアクセスしてみる
postgresサーバに接続
% psql -h localhost -p 5433 -U postgres Password for user postgres: psql (14.1) Type "help" for help.
イメージが初回起動したときに作成されたデータベース(test_db)に接続し,membersテーブルからデータを取得する.
postgres=# \c test_db You are now connected to database "test_db" as user "postgres". test_db=# \dt List of relations Schema | Name | Type | Owner --------+---------+-------+---------- public | members | table | postgres (1 row) test_db=# select * from members; id | name ----+------------- 1 | John Smith 2 | Richard Roe (2 rows) test_db=# \q
最後に (後片付け)
期待通りに動いたのを確認した後は以下のようにコンテナを終了してボリュームも一緒に削除した.
[~/Code/Project/proj_y2021/proj1229/docker-postgres] % docker-compose down --rmi all --volumes
Docker(docker-compose)でMySQLの環境構築をする覚書
はじめに
最近Dockerの使い方を勉強している.MySQLの導入のために環境構築の仕方を試してみた.
特に参考にしたのが以下
"サンプルデータがあらかじめ入った MySQL を Docker で作成する (https://www.xlsoft.com/jp/blog/blog/2019/10/09/post-7617/)"
"[Docker] Mac に docker-compose で MySQL 環境を構築する (https://blog.hiros-dot.net/?p=10469)"
やったこと
基本的なことだが,mysqlのサーバをDockerで立ち上げて,作成したサンプルのデータベースにmysqlクライアントでアクセスするまでを行った.
やったことの手順
- (1) まずベースとなるMySQLのイメージを以下のようにpullした
docker pull mysql:8.0
(2) 最終的にイメージをビルドするのに必要なファイル(
docker-compose.yml
, etc.)を作成(3) イメージをビルド,コンテナを起動
(4) mysqlクライアントでアクセスし,データベースを操作して遊んでみる
フォルダ構成
環境構築は以下の構成で進める.
[~/Code/Project/proj_y2021/proj1229/project-docker/docker-mysql] % tree . . ├── docker-compose.yml └── mysql ├── Dockerfile ├── db │ ├── 01_init_db.sql │ └── 02_insert_data.sql └── my.cnf 2 directories, 5 files
コンテナ起動時に実行される".sql"ファイルについて
本実験では簡単なデータベースを作成してmysqlクライアントでアクセスするまでをやりたかった.
コンテナ内のdocker-entrypoint-initdb.d
に配置されて実行される.sqlファイルは参考サイトで取り上げられていた01_init_db.sqlと02_insert_data.sqlが良さげだったので,今回はこれらを使うことにした.
作成した各ファイルについて
docker-compose.yml
[~/Code/Project/proj_y2021/proj1229/project-docker/docker-mysql] % cat docker-compose.yml version: "3" services: mysqldb: container_name: hello-mysql-db build: ./mysql volumes: - ./mysql/db:/docker-entrypoint-initdb.d/ ports: - "3307:3306"
mysql/db
にはコンテナ起動時に実行したい.sqlファイル群が置かれているが,これらはdocker-entrypoint-initdb.d
にマウントされ,コンテナ起動時に実行されることとなる.
コンテナを起動後にローカルPCの3307ポートからアクセスする予定だったので,上記の"ports:"の設定にした.
Dockerfile
[~/Code/Project/proj_y2021/proj1229/project-docker/docker-mysql] % cat mysql/Dockerfile FROM mysql:8.0 ENV MYSQL_ROOT_PASSWORD=secretpw ADD ./my.cnf /etc/mysql/conf.d/my.cnf
my.cnf
[~/Code/Project/proj_y2021/proj1229/project-docker/docker-mysql] % cat mysql/my.cnf [mysqld] character-set-server=utf8mb4 collation-server=utf8mb4_general_ci [client] default-character-set=utf8mb4
Dockerイメージのビルドとコンテナの起動
以下のようにしてビルド
[~/Code/Project/proj_y2021/proj1229/project-docker/docker-mysql] % docker-compose build Building mysqldb [+] Building 0.1s (7/7) FINISHED => [internal] load build definition from Dockerfile 0.0s => => transferring dockerfile: 36B 0.0s => [internal] load .dockerignore 0.0s => => transferring context: 2B 0.0s => [internal] load metadata for docker.io/library/mysql:8.0 0.0s => [internal] load build context 0.0s => => transferring context: 27B 0.0s => [1/2] FROM docker.io/library/mysql:8.0 0.0s => CACHED [2/2] ADD ./my.cnf /etc/mysql/conf.d/my.cnf 0.0s => exporting to image 0.0s => => exporting layers 0.0s => => writing image sha256:c5da021b5c42d0ef0edf95c1a4c162675ede9c927420f3a6ced8694860fd72d4 0.0s => => naming to docker.io/library/docker-mysql_mysqldb 0.0s Use 'docker scan' to run Snyk tests against images to find vulnerabilities and learn how to fix them
コンテナの起動
[~/Code/Project/proj_y2021/proj1229/project-docker/docker-mysql] % docker-compose up -d Creating network "docker-mysql_default" with the default driver Creating hello-mysql-db ... done
MySQLサーバにクライアントでアクセスしてデータベースで遊んでみる
ローカルのポート(今回は3307)からアクセスする.
[~] % mysql -h 127.0.0.1 -P 3307 -u root -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 8 Server version: 8.0.27 MySQL Community Server - GPL Copyright (c) 2000, 2021, Oracle and/or its affiliates. Oracle is a registered trademark of Oracle Corporation and/or its affiliates. Other names may be trademarks of their respective owners. Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
コンテナ起動時に作成されたデータベースへのアクセス
サンプルとして作成したデータベースからデータが読めるのを確認.
mysql> use jpaddress Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql> SELECT * FROM city WHERE Zip LIKE '108007%'; +-------+---------+---------------+---------+--------------------------------------+------------+-----------+-------------------------------+ | ID | Zip | JP-Prefecture | JP-City | JP-Address | Prefecture | City | Address | +-------+---------+---------------+---------+--------------------------------------+------------+-----------+-------------------------------+ | 37960 | 1080075 | 東京都 | 港区 | 港南 (次のビルを除く) | Tokyo To | Minato Ku | Konan (次のビルを除く) | | 38064 | 1080072 | 東京都 | 港区 | 白金 | Tokyo To | Minato Ku | Shirokane | | 38065 | 1080071 | 東京都 | 港区 | 白金台 | Tokyo To | Minato Ku | Shirokanedai | | 38068 | 1080074 | 東京都 | 港区 | 高輪 | Tokyo To | Minato Ku | Takanawa | | 38389 | 1080073 | 東京都 | 港区 | 三田 (次のビルを除く) | Tokyo To | Minato Ku | Mita (次のビルを除く) | +-------+---------+---------------+---------+--------------------------------------+------------+-----------+-------------------------------+ 5 rows in set (0.09 sec) mysql>
試しにデータベースを作ってみる
データベースsample_db
を作成.
mysql> CREATE DATABASE sample_db; Query OK, 1 row affected (0.01 sec)
新規作成したデータベースを使用(use)する.
mysql> use sample_db Database changed
テーブルの作成とデータの追加
mysql> CREATE TABLE test_tbl ( -> id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, -> name TEXT NOT NULL); Query OK, 0 rows affected (0.02 sec) mysql> INSERT INTO test_tbl (name) VALUES ("HIRO"),("Steve"),("michel"); Query OK, 3 rows affected (0.02 sec) Records: 3 Duplicates: 0 Warnings: 0
作成したテーブルからデータを読めるのを確認した.
mysql> select * from test_tbl; +----+--------+ | id | name | +----+--------+ | 1 | HIRO | | 2 | Steve | | 3 | michel | +----+--------+ 3 rows in set (0.01 sec) mysql>
(最後に) 後片付け
今回は環境構築の仕方を試しに色々実験しただけだったので,終わった後は後片付けとして作成されたコンテナやボリュームを削除して終わりとする.
以下を実行し,docker-composeで起動したコンテナは終了され,ボリュームも削除されたことを確認した.
docker-compose down --rmi all --volumes
今回はここまでにする.SQLもうちょっと勉強したほうがいいかも.
RustでWebAssemblyに軽く入門した
はじめに
これはあくまで個人的な日々の記録であり備忘録です.WebAssemblyに興味があり,Rustで使えるとのことなので,使ってみました.メモを書き残しておきます.
以下の参考サイトを習っています.
やったのは,"alert"で警告ダイアログのポップアップを出すだけでJavaScriptでも書ける内容ですが,今回はそれをRustで書いたコードから生成されたwasmファイルに処理を書いていて,それをjavascriptから呼び出す感じの処理を書いています.
この記事はあくまで備忘録ですが,以下の手順でメモしています.
環境構築
まず,使ったRustとcargoのバージョンは以下です.共に1.55.0です.以下のようなコマンドで確認できます.
[~] % rustc -V rustc 1.55.0 (c8dfcfe04 2021-09-06) [~] % cargo --version cargo 1.55.0 (32da73ab1 2021-08-23) [~] %
Rustで書いたコードをwasmにコンパイルする,wasm-packが必要らしいです.以下のようにcargo installで入れます.
cargo install wasm-pack
使ってみる
cargo newで"--lib"オプションをつけて,ライブラリ用のターゲットを作ります.cargo newに関するオプションの詳細は公式docから確認可能
名前はhi-wasmにしました(helloじゃなくてhiにしただけで特に意味はないです).
cargo new --lib hi-wasm
wasmにコンパイルされるコードをRustで書く
src/lib.rs
にRustでWebAssemblyで実行するコードを書いてみる.
[~/Code/Project/Project_WebAssembly/hi-wasm] % cat src/lib.rs use wasm_bindgen::prelude::*; #[wasm_bindgen] extern "C" { pub fn alert(s: &str); } #[wasm_bindgen] pub fn greet(name: &str) { alert(&format!("Hi, {}!", name)); }
wasm-bindgenというのが気になったのだけれど,これは"RustとJavascriptの間でやり取りする"ために必要であり,JavascriptからRustで書いたAPI(上記コードでいうgreet)を呼び出せるそうです.
このときのCargo.toml
Cargo.toml
は以下のようにする.
[~/Code/Project/Project_WebAssembly/hi-wasm] % cat Cargo.toml [package] name = "hi-wasm" version = "0.1.0" edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] crate-type = ["cdylib"] [dependencies] wasm-bindgen = "0.2"
Rustコードをwasmへコンパイルする
wasm-pack build
コマンドで,上記のRustのコード(src/lib.rs)をwasmにコンパイルする.参考サイトにならい,"--target web"オプションをつけた.
[~/Code/Project/Project_WebAssembly/hi-wasm] % wasm-pack build --target web
これを機にwasm-packについて調べた
wasm-packというのを初めて使ったし,"--target web"という書き方がよく分からなかったので調べてみた.公式docを読んで知ったのが以下.
オプション | 使用法(usge) | 説明(description) |
---|---|---|
web | Native in browser(ブラウザネイティブ?) | Outputs JS that can be natively imported as an ES module in a browser, but the WebAssembly must be manually instantiated and loaded. (ESモジュールとしてブラウザにネイティブにインポートできるJSファイルを出力する.ただし,WebAssemblyはマニュアルにインスタンス化されロードされなければならない) |
webオプションは,ブラウザにロードされるJSファイルを出力するという意味らしいです.
ちなみに,wasm-pack buildは以下のようなコマンドらしいです.
The wasm-pack build command creates the files neccessary for JavaScript interoperability and for publishing a package to npm. This involves compiling your code to wasm and generating a pkg folder. This pkg folder will contain the wasm binary, a JS wrapper file, your README, and a package.json file.
「"wasm-pack build"は,Javascriptとの相互運用性に必要なファイル群を作成するためのコマンドであり,また,packageをnpmで配布するためのコマンドです.これはあなたのコードをwasmへとコンパイルし,pkgフォルダを生成します.pkgフォルダはwasmバイナリ,JSラッパーファイル,そしてREADMEとpackage.jsonファイルを含んでいます.」
wasmで書いた処理をjavascriptから呼び出すwebページにアクセスしてみる
参考サイトに習い,index.htmlを作ります.pkg以下にあるhi_wasm.jsをインポートしてます.wasmで書いた処理を呼び出すjsコードみたいに書けそうですね
[~/Code/Project/Project_WebAssembly/hi-wasm] % cat index.html <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>hi-wasm example</title> </head> <body> <script type="module"> import init, {greet} from "./pkg/hi_wasm.js"; init() .then(() => { greet("WebAssembly") }); </script> </body> </html>
webサーバを立ててアクセスする
どこかで見かけた知恵ですが,pythonでwebサーバを立てると,localhost:8000からアクセスできます.
アラートボックス( "Hi WebAssembly"が出たら,ここでは成功です.(普通にjavascriptでも書けそうな処理ですが)
[~/Code/Project/Project_WebAssembly/hi-wasm] % python3 -m http.server
感想と今後の展望
npmは慣れてなかったんだけど,python3でwebサーバを立ててもカジュアルにアクセスできるってことが分かったので,敷居が下がった.
チュートリアルが分かりやすくて学習に助りましたありがとう.こんな感じか,って分かった.使いこなせるようになりたい.本腰入れて公式のドキュメントとかからじっくり読もう
もっと勉強して使いこなせるようになりたいと思った.
参考文献
本記事はこれを実践してました.
wasm-packのコマンドについて気になったので調べるのに見ていました.
ブログを開いた
誰にとって有益になるかは分からないけど,情報発信とか少しずつ始めてみようと思って開いた.