はじめに
私は普段SREとしてAWSやEKS(Kubernetes)などのクラウドインフラの運用に従事しています。クラウド技術を支える代表的な言語としてGo言語が挙げられます。本記事では自身が学んできたGo言語のテクニックについて紹介します。
OSSのソースコードリーディングやコントリビュート、業務委託でGoを読んだり、書いたりしていますが、まともにGoをレビューしていただける環境での開発経験がないので誤りがあるかもしれません。ご了承ください。
コンストラクタ関数
Goではfunc NewUser(name string, age int) *User
のようなメソッドがコンストラクタのような役割をします。また、レシーバを使うことでgetterであるfunc (u User) GetName() string
、setterであるfunc (u *User) SetName(name string)
が提供できます。
package main
import "log"
type User struct {
name string
age int
}
func NewUser(name string, age int) *User {
return &User{
name: name,
age: age,
}
}
// 値レシーバ
func (u User) GetName() string {
return u.name
}
// ポインタレシーバ
func (u *User) SetName(name string) {
u.name = name
}
func main() {
user := NewUser("John", 20)
log.Println(user.GetName()) // John
user.SetName("Doe")
log.Println(user.GetName()) // Doe
}
継承(構造体の埋め込み)
Goでは構造体を埋め込むことで継承のようなものを実現できます。子構造体のフィールドにアクセスすることができます。
package main
import (
"log"
)
// 子構造体
type Computer struct {
memorysize int
cpu string
}
// 親構造体
type NotePC struct {
Computer
battery int
Weight int
}
func main() {
notePc := NotePC{
Computer: Computer{
memorysize: 8,
cpu: "intel",
},
battery: 5000,
Weight: 1000,
}
log.Println(notePc.memorysize) // 8
}
interfaceの実装漏れを防ぐ
コンストラクタ関数でinterface型を返すようにするとinterfaceの実装漏れに気づくことができます。var _ UserService = &User{}
としてもメモリ領域を確保せず、interfaceの実装漏れを防ぐことができます。
package main
import "log"
type UserService interface {
Greeting() string
}
type User struct {
Name string
Age int
}
// 戻り値はinterface型
func NewUser(name string, age int) UserService {
return &User{
Name: name,
Age: age,
}
}
func (u *User) Greeting() string {
return "Hello, " + u.Name + "!"
}
func main() {
user := NewUser("John", 20)
log.Println(user.Greeting())
}
依存性注入と逆転
Goでは、以下の2点を実現することで、UserUsecase構造体 -> UserRepositoryインターフェース <- UserRepository構造体
の依存関係を作ることができ、DIとDIPを実現することができます。
- UserUsecaseコンストラクタにUserRepositoryを渡す
- UserUsecase構造体にUserReposiotryインターフェースを埋め込む
package main
import (
"log"
)
type User struct {
Id int
Name string
}
type UserReposiotry interface {
GetUsers() ([]User, error)
}
type UserRepositoryImpl struct {}
func NewUserRepository() UserReposiotry {
return &UserRepositoryImpl{}
}
func (ur *UserRepositoryImpl) GetUsers() ([]User, error) {
return []User{
{Id: 1, Name: "user1"},
{Id: 2, Name: "user2"},
{Id: 3, Name: "user3"},
}, nil
}
type UserUsecaseImpl struct {
repository UserReposiotry
}
func NewUserUsecae(repository UserReposiotry) *UserUsecaseImpl {
return &UserUsecaseImpl{repository: repository}
}
func (uu *UserUsecaseImpl) GetUsers() ([]User, error) {
return uu.repository.GetUsers()
}
func main() {
repository := NewUserRepository()
// UserUsecaseコンストラクタにUserRepositoryを渡す
usecase := NewUserUsecae(repository)
users, err := usecase.GetUsers()
if err != nil {
log.Println(err)
}
for _, user := range users {
log.Println(user)
}
}
三項演算子みたいなやつ
Goには三項演算子がありません。if文を使って以下のような書き方で実装することが多いです。
message := "不合格です"
if point > 70 {
message = "合格です"
}
Enumみたいなやつ
GoにはEnumがありません。iotaを使うことで列挙型を実現できます。
package main
import "log"
type Color int
const (
Red Color = iota
Blue
Yellow
)
func (c Color) String() string {
switch c {
case Red:
return "Red"
case Blue:
return "Blue"
case Yellow:
return "Yellow"
default:
return "Unknown"
}
}
func main() {
var c Color = Red
log.Println(c.String()) // Red
}
並行処理を行う
SQSからメッセージをポーリングし、処理するworkerの例です。goroutineを使うことで簡単に平行処理を簡単に書くことができます。
package main
import (
"context"
"log"
"sync"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/sqs"
"github.com/aws/aws-sdk-go-v2/service/sqs/types"
)
func NewSqs() (*sqs.Client, error) {
cfg, err := config.LoadDefaultConfig(context.TODO(),
config.WithRegion("ap-northeast-1"),
)
if err != nil {
return &sqs.Client{}, err
}
return sqs.NewFromConfig(cfg), nil
}
func main() {
client, err := NewSqs()
if err != nil {
panic(err)
}
queueURL, err := client.GetQueueUrl(context.TODO(), &sqs.GetQueueUrlInput{
QueueName: aws.String("sqs-name"),
})
if err != nil {
panic(err)
}
for {
resp, err := client.ReceiveMessage(context.TODO(), &sqs.ReceiveMessageInput{
QueueUrl: queueURL.QueueUrl,
})
if err != nil {
log.Printf("Error receiving messages: %v\n", err)
continue
}
var wg sync.WaitGroup
for _, message := range resp.Messages {
wg.Add(1)
go func(message types.Message) {
defer wg.Done()
_, err := client.DeleteMessage(context.TODO(), &sqs.DeleteMessageInput{
QueueUrl: queueURL.QueueUrl,
ReceiptHandle: message.ReceiptHandle,
})
if err != nil {
log.Printf("Error deleting message: %v\n", err)
}
}(message)
}
wg.Wait()
}
}
おわりに
Go言語の実装テクニックについて書きました。誤りや他にもいいテクニックがあれば教えていただきたいです。