2025年のバックエンド選定で、Goは有力な候補です。
理由は「どの処理でも最速だから」ではありません。言語仕様が比較的小さく、並行処理を記述しやすく、HTTPサーバーを標準ライブラリで作れ、単一の実行ファイルとして配布しやすいからです。
一方、既存チームの経験や業務領域を無視して選べる万能言語でもありません。
この記事ではGo 1.22と1.23の代表的な変化を確認しながら、2025年のバックエンドでGoを選ぶ条件を整理します。
2025年にGoが候補になる理由
Goの強みは、個別機能より組み合わせにあります。
- 読み方を揃えやすい単純な構文
- goroutineによる並行処理
- HTTP、JSON、暗号、テストなどの標準ライブラリ
- クロスコンパイルしやすい単一バイナリ
- formatter、test、vet、profileを含む公式tooling
APIサーバーやネットワークサービスでは、これらを同時に使います。
採用判断では、人気や印象ではなく「チームが運用するサービスに、この組み合わせが合うか」を見ます。
単純さは共同開発のための機能
Goは、書き方の選択肢を意図的に絞っています。
たとえば公式formatterの gofmt によって、インデントや空白の議論を減らせます。
gofmt -w .
go test ./...
go vet ./...
コードレビューでは書式より、エラー処理、境界条件、データ競合、API設計に時間を使えます。
明示的なエラー処理も特徴です。
user, err := repository.Find(ctx, id)
if err != nil {
return User{}, fmt.Errorf("find user: %w", err)
}
失敗経路が行として見えるため、ログやHTTP statusへの変換位置を追いやすくなります。
ただし、同じ形の if err != nil が増え、冗長に感じる場面もあります。短さより制御の見通しを優先する設計です。
単純さにも学習が必要
構文が小さいことと、本番コードが簡単であることは同じではありません。
次の設計は別途学ぶ必要があります。
context.Contextによるcancelとdeadline- goroutineの終了条件
- channelのclose責任
- interfaceを置く境界
- errorのwrapと分類
- database transaction
- graceful shutdown
Goを選ぶだけで設計上の問題が消えるわけではありません。
goroutineは軽量な並行実行単位
goroutineは go を付けて関数を並行実行する仕組みです。
go sendAuditLog(event)
OS threadを直接1本ずつ管理するより軽量で、Go runtimeが多数のgoroutineをthreadへ割り当てます。
複数の外部APIを待つ処理や、connectionごとに仕事を扱うserverと相性があります。
profileCh := make(chan Profile, 1)
ordersCh := make(chan []Order, 1)
go func() {
profileCh <- fetchProfile(ctx)
}()
go func() {
ordersCh <- fetchOrders(ctx)
}()
profile := <-profileCh
orders := <-ordersCh
この例は値だけを返す形に絞っています。実際にはcontextによるcancelと、各処理のerrorを呼び出し元へ返す経路も設計します。channelを一つにまとめて受信順に変数へ代入すると、どちらの結果か取り違えるため、型または識別子で対応付けます。
重要なのは「並行にした」だけでは安全にならない点です。
共有memoryへ複数goroutineが同時に書けばdata raceが起きます。終了しないgoroutineはresourceを保持し続けます。
go test -race ./...
race detectorは有力な検査ですが、実行した経路だけを観察します。設計とtestの代わりにはなりません。
標準ライブラリだけでHTTP APIを始められる
Goの net/http にはserverとclientの両方があります。
Go 1.22では ServeMux のpatternが拡張され、HTTP methodとwildcardを表せるようになりました。
package main
import (
"encoding/json"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("GET /users/{id}", func(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"id": id})
})
http.ListenAndServe(":8080", mux)
}
小さなAPIなら、routingのためだけにframeworkを追加せず始められます。
標準ライブラリにはJSON、SQL、TLS、structured logging、test、contextも含まれます。依存を減らせることは、更新対象や供給網riskを減らす助けになります。
ただし、認証、validation、migration、observabilityまで全部標準で完成するわけではありません。必要な外部packageは、保守状況と更新方針を確認して採用します。
単一バイナリは配布を単純にする
Goは通常、依存を含む実行ファイルへcompileできます。
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o app ./cmd/api
実行環境へ言語runtimeやpackage managerを別途用意せず、binaryを起動できます。
containerでも構成を小さくしやすく、起動手順を揃えやすい点が利点です。
FROM gcr.io/distroless/static-debian12
COPY app /app
USER nonroot:nonroot
ENTRYPOINT ["/app"]
ただし、次には注意が必要です。
- CA証明書やtimezone dataが必要か
- cgoを使う依存がないか
- target OSとarchitectureが正しいか
- debug用shellがない環境をどう観測するか
単一binaryは運用を単純にしますが、監視や設定管理を不要にはしません。
toolingが開発cycleを揃える
Goは基本的な開発toolを公式配布に含めます。
go mod tidy
gofmt -w .
go vet ./...
go test ./...
go test -race ./...
go build ./...
CPUやmemoryを調べる pprof、実行中のgoroutineやblockingを調べるtraceも標準のecosystemにあります。
言語、build、test、format、module管理の入口が揃っているため、repositoryごとの手順差を減らせます。
一方、静的解析やsecurity scanは追加toolも必要です。公式toolだけで品質保証が完了すると考えないことが大切です。
Go 1.22の代表的な変更
Go 1.22は2024年2月に公開されました。
バックエンド開発で分かりやすい変更は3つです。
loop変数がiterationごとに作られる
以前はloop変数をclosureから参照すると、意図せず同じ変数を共有するbugが起きやすい状態でした。
Go 1.22では、該当するmoduleで各iterationに新しい変数が作られます。
for _, user := range users {
go func() {
notify(user)
}()
}
ただし、挙動はmoduleの go versionと関係します。upgrade時は go.mod とtestを確認します。
integerへrangeできる
for i := range 3 {
fmt.Println(i)
}
0、1、2 の順に処理できます。
ServeMuxのpatternが強化された
mux.HandleFunc("DELETE /users/{id}", deleteUser)
methodとpath parameterを標準routerで扱えます。
既存patternとの競合や波括弧の扱いが変わる可能性があるため、古いrouterから切り替えるときはroute testを実行します。
Go 1.23の代表的な変更
Go 1.23は2024年8月に公開されました。
range-over-function iterator
function形式のiteratorを range で扱えるようになりました。
func IDs(users []User) iter.Seq[int64] {
return func(yield func(int64) bool) {
for _, user := range users {
if !yield(user.ID) {
return
}
}
}
}
利用側は通常のrangeに近い形です。
for id := range IDs(users) {
fmt.Println(id)
}
独自collection APIを作る選択肢が増えましたが、単純なsliceで十分ならiteratorを導入する必要はありません。
timerとtickerの変更
Go 1.23では、該当するmoduleで回収されていないTimerやTickerをgarbage collectorが回収できるよう改善されました。
またtimer channelは同期channelになり、Reset や Stop 後の古い値に関する挙動が改善されています。
既存コードがchannelのcapacityや古い非同期挙動へ依存していないか、version更新時にtestします。
iter、slices、maps
標準ライブラリに iter packageが加わり、slices と maps にiterator関連functionが追加されました。
新機能は便利ですが、upgrade理由を「新しいから」にしません。security fix、toolchain support、依存moduleの要件も含めて更新計画を立てます。
Goを選ぶときのtrade-off
成熟したframeworkの機能量
別言語の大規模frameworkには、ORM、管理画面、認証、form処理などが統合されている場合があります。
Goは必要な部品を選び、applicationの境界を自分で設計する場面が多くなります。
短期間で業務画面を大量に作る案件では、既存frameworkの方が速いことがあります。
型表現とdomain modeling
Goの型は実用的ですが、高度な代数的data typeやpattern matchingを中心に設計したいteamには物足りない可能性があります。
ecosystemと採用
既存system、teamの経験、採用市場、社内libraryを無視した言語変更はcostを増やします。
Go自体の習得だけでなく、monitoring、deployment、incident対応まで含めた運用能力を見ます。
向いている場面
- HTTP APIやgRPC service
- network proxyやgateway
- 多数のI/O待ちを扱うservice
- command line tool
- 小さなcontainer imageと単純な配布を重視する環境
- 複数teamで書き方を揃えたいcodebase
既存frameworkへ強く依存する業務system、data science、GUI、極端なreal-time制約では慎重に比較します。実装可能かではなく、他の選択肢より総costが低いかで判断します。
選定checklist
workload
- I/O待ちの多い処理か
- 必要なthroughputとlatencyを計測できるか
- CPU、memory、connection数の上限は何か
team
- error、context、goroutineをreviewできるか
- data raceとgoroutine leakをtestできるか
- on-call担当がprofileを読めるか
ecosystem
- database driverとmigration toolが要件を満たすか
- 認証、observability、code generationの候補が保守されているか
- 既存serviceとのprotocolを共有できるか
operation
- target OSとarchitectureを管理できるか
- graceful shutdownとhealth checkを実装できるか
- Go versionとmodule更新を継続できるか
移行では、書き直す効果、小さく試す境界、rollback方法を確認します。不明点が多ければ全面移行より小さな検証から始めます。
導入は小さく検証する
候補serviceを1つ選び、同じtraffic条件で比較します。
見る値は単純なrequest/secondだけではありません。
- 実装とreviewにかかった時間
- binaryとcontainerのsize
- 起動時間
- p95・p99 latency
- memoryとCPU
- error率
- deployとrollbackの容易さ
- 障害時の調査時間
公開benchmarkではなく、自分たちのdatabase、認証、loggingを含む条件で測ります。
まとめ
2025年のGoは、backendの有力候補です。
価値の中心は、単純な言語、goroutine、充実した標準library、単一binary、統一されたtoolingの組み合わせにあります。
Go 1.22ではloop変数、integer range、ServeMuxが改善され、Go 1.23ではrange-over-functionやtimer関連が更新されました。
ただし、新機能や人気だけでは採用理由になりません。
teamの経験、既存資産、必要なlibrary、運用方法、実workloadの計測を揃え、小さなserviceから判断するのが現実的です。