Goとは
GoはGoogleが開発したプログラミング言語であり、静的型付け、C言語に則ったコンパイル言語、メモリ安全性、ガベージコレクション、並行性の特徴を持ちます。
主にWebアプリケーションのバックエンド開発に使われます。それは、型安全性や開発ツールの豊富さ、パフォーマンスの優位性、Dockerとの相性が良いことなどが理由です。
Goのインストール
Ubuntu上でGoをインストールします。以下のコマンドで最新バージョンの1.23.4をダウンロードします。
Ubuntu 22.04.5 LTS/Intel 64bitの場合
wget https://go.dev/dl/go1.23.4.linux-amd64.tar.gz
その他のバージョンについては以下のサイトを参照してください。
ダウンロードしたファイルを/usr/local
に展開します。
tar -C /usr/local -xzf go1.23.4.linux-amd64.tar.gz
既にGoがインストールされている場合
インストールされているGoのバージョンを確認します。
go version
/usr/local/go
にインストールされている古いバージョンのGoを削除し、最新バージョンを展開します。
rm -rf /usr/local/go && tar -C /usr/local -xzf go1.23.4.linux-amd64.tar.gz
/usr/local/go/bin
を$PATH
に追加します。$HOME/.profile
をVSCodeで開きパスを追加します。
code $HOME/.profile
以下の行を追加してください。
export PATH=$PATH:/usr/local/go/bin
バージョンを確認します。
go version
go version go1.23.4 linux/amd64
と表示されればインストール成功です。
Hello World
作業ディレクトリgolang-intro
を作成し、main.go
を作成します。
main.go
に以下のコードを記述します。
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
実行方法
Goはコンパイル言語なので、先にコンパイルを行い実行用のファイルを作成する必要があります。
go run
コマンドを使うことで「コンパイル」と「実行」を同時に行うことができます。
go run main.go
Hello, World!
と表示されれば成功です。
Goの基礎文法
押さえておきたいGoの基礎文法を簡単に紹介します。
パッケージ宣言
すべてのGoファイルはパッケージ宣言から始まり、mainパッケージがプログラムの開始点です。
package main
インポート宣言
外部パッケージを利用するための宣言です。複数のパッケージをインポートする場合は()
で囲みます。
import (
"fmt"
"time"
"os"
_ "github.com/go-sql-driver/mysql"
)
_
はブランク識別子と呼ばれるものでインポートしたパッケージと依存関係のあるパッケージをインポートする際に使われます。
Goでは、インポートしたパッケージをコード内で使わない場合はコンパイルエラーとなってしまうので依存関係を解決するためのインポートであることを明示しなければなりません。
主なデータ型
Goの主なデータ型は以下の通りです。
データ型 | 説明 | 例 |
---|---|---|
bool | 真偽値 | true, false |
string | 文字列 | "Hello, World!" |
int | 整数 | 100 |
float64 | 浮動小数点数 | 3.14 |
byte | バイト, 符号なし8ビット整数 uint8 の別名 | 'A' |
変数宣言
変数の宣言と同時に特定の値で初期化するには次のように記述します。
var 変数名 データ型 = 初期値
var message string = "Hello, World!"
var number int = 100
var isTrue bool = true
Goは型推論が可能なので、データ型を省略することもできます。
var message = "Hello, World!"
var number = 100
var isTrue = true
:=
を使ってvar
を省略することもできます。(Goではこの書き方がよく使われるため慣れておきましょう)
message := "Hello, World!"
number := 100
isTrue := true
配列とスライス
Goの配列は宣言した時点で型とサイズが決められます。それに対して、スライスはサイズは決められておらず必要に応じて要素を追加できます。
スライスに要素を追加するにはappend
関数を使います。
// 配列
numbers := [3]int{1, 2, 3}
fmt.Println(numbers) // [1 2 3]
fmt.Println(numbers[0]) // 1
// スライス
foods := []string{"apple", "banana", "orange"}
// 要素の追加
foods = append(foods, "grape")
fmt.Println(foods) // [apple banana orange grape]
map
マップはキーと値のペアを持つデータ構造です。
map[キーのデータ型]値のデータ型
で宣言します。
prices := map[string]int{
"apple": 100,
"banana": 200,
"orange": 150,
}
// bananaの値を取得
fmt.Println(prices["banana"]) // 200
補足:Goのmap
はHashMapでありSetの構文はないためmap[string]interface{}
を使うことでSetを実現することができます。
tuple
関数の戻り値が複数の場合、複数の値を返すことができます。
func getValues() (int, int) {
return 1, 2
}
a, b := getValues()
fmt.Println(a, b) // 1 2
nilとゼロ値
Goにおけるnil
とゼロ値は異なる目的で使用されますがどちらも「初期化」や「未設定」を意味する値です。
Goにおける
nil
は、ポインタ、スライス、マップ、チャネル、インターフェースといった特定のデータ型で「無効」または「未設定」を意味する値として使用されます。
https://ittrip.xyz/go/go-nil-zero-valuesより引用
ゼロ値は変数が初期化されていない場合に自動的に割り当てられる値です。例えば、数値型であれば0
、文字列型であれば""
、ブール型であればfalse
です。
Goの関数では通常、戻り値としてエラーを返し、エラーかどうかを判定するためにnil
を使います。
// text.txtファイルを開く
file, err := os.Open("./text.txt")
if err != nil {
fmt.Println("ファイルを開くことができませんでした") // エラーが発生した場合はerrはnilではない
return
} else {
fmt.Println("ファイルを開くことができました")
}
MySQLとは
MySQLは世界で最も人気があり、広く使われているオープンソースのリレーショナルデータベース(RDBMS)です。
Web研では以前、SQLiteを使ったデータベースの講義を行いました。SQLiteはサーバーレスつまりファイルベースであり、直接ファイルに読み書きを行います。そのため、インストールや設定が不要であり、自己完結型でOSへの依存度が低いのが特徴です。
一方、MySQLはクライアントサーバーモデルに準拠しており、実行するにはサーバーが必要となります。MySQLはプラットフォームに依存しないため、どのOSでも動作し、様々なプログラミング言語と互換性があるのが特徴です。
Web研の第8回の講義内でデータベースに関する詳しい説明があります。
Dockerとは
Dockerはコンテナ型の仮想環境を作成・実行・管理するためのプラットフォームです。
以前にDocker入門講習会を行いましたので、詳しい説明は以下の記事を参照してください。
Dockerのインストール方法
Docker Desktopをインストールします。
Docker Desktopを起動し、Settings -> Resources -> WSL Integration
からWSL2を有効にします。
DockerでGoとMySQLを繋ぐ
Dockerを用いてGoとMySQLを繋いでみましょう。
作業ディレクトリsample-go-mysql-connect-app
を作成し、次の4つのファイルを作成します。
Dockerfile
(Goのイメージをビルドするためのファイル)docker-compose.yml
(GoとMySQLのコンテナを起動するためのファイル)main.go
(Goのプログラム)init.sql
(MySQLの初期設定)
ファイル階層は以下の通りです。
sample-go-mysql-connect-app
├── Dockerfile
├── docker-compose.yml
├── main.go
└── init.sql
補足:本来はdevelopment/productionを分け、developmentはvolume mountしてrestartだけでサーバーを更新できるようにしたほうが良いです。今回はファイル階層を単純にするためにproduction用のDockerfileを作成します。
# ベースイメージ
FROM golang:1.23
# ワーキングディレクトリの設定
WORKDIR /app
# モジュールファイルをコピーして依存関係をインストール
COPY go.mod go.sum ./
RUN go mod download
# ソースコードをコピーしてビルド
COPY . .
RUN go build -o main .
# アプリケーションを起動
CMD ["./main"]
version: '3.8'
services:
mysql:
image: mysql:8.4
container_name: sample-go-mysql-connect-app-mysql
restart: always
environment:
MYSQL_ROOT_PASSWORD: root
MYSQL_DATABASE: testdb
MYSQL_USER: testuser
MYSQL_PASSWORD: testpassword
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./init.sql:/docker-entrypoint-initdb.d/init.sql:ro
app:
build: .
container_name: sample-go-mysql-connect-app
ports:
- "8080:8080"
depends_on:
- mysql
environment:
DB_HOST: mysql
DB_USER: testuser
DB_PASSWORD: testpassword
DB_NAME: testdb
volumes:
mysql_data:
package main
import (
"database/sql" // データベースの操作
"encoding/json" // JSONのエンコードとデコード
"fmt"
"log"
"net/http" // HTTPでWebサーバーを立てる
"os"
_ "github.com/go-sql-driver/mysql"
)
// 構造体を定義
type Item struct {
ID int `json:"id"`
Name string `json:"name"`
}
func main() {
// 環境変数からDB接続情報を取得
dbHost := os.Getenv("DB_HOST")
dbUser := os.Getenv("DB_USER")
dbPassword := os.Getenv("DB_PASSWORD")
dbName := os.Getenv("DB_NAME")
dsn := fmt.Sprintf("%s:%s@tcp(%s:3306)/%s", dbUser, dbPassword, dbHost, dbName)
db, err := sql.Open("mysql", dsn)
if err != nil {
log.Fatalf("Failed to connect to database: %v", err)
}
defer db.Close()
// itemsにGETリクエストがあった場合の処理
http.HandleFunc("/items", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// データベースからデータを取得
rows, err := db.Query("SELECT id, name FROM items")
// エラーハンドリング
if err != nil {
http.Error(w, fmt.Sprintf("Database query failed: %v", err), http.StatusInternalServerError)
return
}
defer rows.Close()
// 取得したデータを格納するスライスを定義
var items []Item
for rows.Next() {
var item Item
if err := rows.Scan(&item.ID, &item.Name); err != nil {
http.Error(w, fmt.Sprintf("Row scan failed: %v", err), http.StatusInternalServerError)
return
}
// 取得したレコードをitemsに追加
items = append(items, item)
}
// JSON形式でレスポンスを返す
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(items); err != nil {
http.Error(w, fmt.Sprintf("JSON encoding failed: %v", err), http.StatusInternalServerError)
}
})
// サーバーを起動
log.Println("Server started on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
次に、新しいGoモジュールを初期化するためのコマンドを実行します。
このコマンドで、モジュール名とモジュールが依存する他のモジュールの情報が含まれるgo.mod
ファイルが作成されます。
go mod init sample-go-mysql-connect-app
モジュールの依存関係を整理するために、以下のコマンドを実行します。
go mod tidy
CREATE TABLE items (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL
);
INSERT INTO items (name) VALUES ('Item 1'), ('Item 2'), ('Item 3');
以下のコマンドでコンテナを起動します。
docker-compose up --build
http://localhost:8080/items
にアクセスすると、以下のようにJSON形式でデータが表示されます。
[
{
"id": 1,
"name": "Item 1"
},
{
"id": 2,
"name": "Item 2"
},
{
"id": 3,
"name": "Item 3"
}
]
コンテナを停止するには、以下のコマンドを実行します。
docker-compose down
やってみよう
サンプルコードを基に次のような課題に取り組んでみましょう。
post
メソッドを追加して、新しいアイテムをデータベースに追加するdelete
メソッドを追加して、アイテムを削除するusers
テーブルやcomments
テーブルなど新しいテーブルを追加する- フロントエンドとバックエンドを繋いでアイテムを追加・削除できるようにする