🎃
golangのpackage分割
サーバーサイドのアプリケーションを作る時にフラットパッケージで作っていたがpackageを切りたくなってきたので、少しメモ。
基本的にモジュールを作りたい場合は基本的にはパッケージルートに置いておくだけですむ。
github.com/somebody/awesome_module
というモジュールをimportして使いたい場合はawsome_module
ディレクトリ直下にまとまっていた方が何かと都合がよい。
これに対しアプリケーションを作っていてそれなりの規模になってくると、packageを切りたくなるが循環参照(cyclic import)問題がつきまとう。
「実用Go言語」の 6.4.2 パッケージを階層化する
に3パターン書いてあるが具体例はなかった。チーム内で検討してみるといいのかもしれない。
1 共通要素のパッケージを作り、依存先をそこに集中させる
2 ルートパッケージのロジックをすべて移動し、ルートパッケージを共通要素の置き場とする
3 共通部分を持たない末端のロジックを子パッケージとして切り出す
以下は自分の考えた具体例
├── cipher
│ └── cipher.go
├── go.mod
├── main.go
├── model
│ ├── converter
│ │ └── converter.go
│ ├── db
│ │ └── user.go
│ └── user.go
└── repository
└── database
└── database.go
cipher/cipher.go
package cipher
import "encoding/base64"
func Encrypt(value string) string {
return base64.StdEncoding.EncodeToString([]byte(value))
}
func Decrypt(value string) (string, error) {
decoded, err := base64.StdEncoding.DecodeString(value)
return string(decoded), err
}
main.go
package main
import (
"fmt"
"main/model"
"main/model/converter"
"main/repository/database"
)
func main() {
{
dbUser := database.NewUser("taro", "taro@sample.com", string(model.UserStatusActive))
fmt.Printf("dbUser: %#v\n", dbUser)
modelUser, err := converter.ConvertToModelUser(dbUser)
if err != nil {
fmt.Printf("error: %s\n", err)
}
fmt.Printf("modelUser: %#v\n", modelUser)
fmt.Println(modelUser.IsActive())
}
fmt.Println()
{
modelUser := model.User{
Name: "taro",
Email: "taro@sample.com",
Status: model.UserStatusInactive,
}
fmt.Printf("modelUser: %#v\n", modelUser)
fmt.Println(modelUser.IsActive())
dbUser := converter.ConvertToDbUser(modelUser)
fmt.Printf("dbUser: %#v\n", dbUser)
}
}
model/converter/converter.go
package converter
import (
"main/cipher"
"main/model"
"main/model/db"
)
func ConvertToModelUser(u db.User) (*model.User, error) {
rawEmail, err := cipher.Decrypt(u.Email)
if err != nil {
return nil, err
}
return &model.User{
Name: u.Name,
Email: rawEmail,
Status: model.UserStatus(u.Status),
}, nil
}
func ConvertToDbUser(u model.User) db.User {
return db.User{
Name: u.Name,
Email: cipher.Encrypt(u.Email),
Status: string(u.Status),
}
}
model/db/user.go
package db
type User struct {
Name string
Email string
Status string
}
model/user.go
package model
type UserStatus string
const (
UserStatusActive UserStatus = "active"
UserStatusInactive UserStatus = "inactive"
)
type User struct {
Name string
Email string
Status UserStatus
}
func (u *User) IsActive() bool {
return u.Status == UserStatusActive
}
repository/database/database.go
package database
import (
"main/cipher"
"main/model/db"
)
func NewUser(name, email, status string) db.User {
return db.User{
Name: name,
Email: cipher.Encrypt(email),
Status: status,
}
}
実行結果
% go run main.go
dbUser: db.User{Name:"taro", Email:"dGFyb0BzYW1wbGUuY29t", Status:"active"}
modelUser: &model.User{Name:"taro", Email:"taro@sample.com", Status:"active"}
true
modelUser: model.User{Name:"taro", Email:"taro@sample.com", Status:"inactive"}
false
dbUser: db.User{Name:"taro", Email:"dGFyb0BzYW1wbGUuY29t", Status:"inactive"}
「ルートパッケージを共通要素の置き場とする」ならmodel/converter/converter.go
はルートに持っていっていいのかもしれない。(main.go
はmain/main.go
みたいに移動する)
Discussion