goa tips: 要素がポインタになるのを避ける

はじめに

Payload の Member が必須要素でない場合,生成される対応するコードはポインタで表現されます. 文字列なら,string のポインタで表されるわけで,ポインタを渡すために変数を用意してポインタで渡してやるみたいなひと手間が必要になってきます. これはややめんどくさいので,要素がポインタになるのを避けよう・・・というものぐさな方針です.

※ もっといい方法があればしりたいです・・・

Payload や MediaType の要素がポインタになるのはどんなときか

Payload や MediaType の要素が(生成されたコードで)ポインタで表現されるのは,要素が必須要素でないときです. たとえば,下記のような Payload では,bottleIDcategory は required ですが,comment は省略可能です.

BottlePayload = Type("BottlePayload", func() {
        Member("bottleID", Integer, "Bottle ID", func(){
                Minimum(0)
                Maximum(127)
        })
        Member("category", String, "Category", func(){
                Enum("red", "whilte", "rose")
                Default("red")
        })
        Member("comment", String, "Comment", func(){
                MaxLength(256)
        })
        Required("bottleID", "category") // ← 必須要素の指定
})

生成される対応するコードは,下記のようになります.Comment*string で表現されてます.

// BottlePayload user type.
type BottlePayload struct {
        // Bottle ID
        BottleID int `form:"bottleID" json:"bottleID" xml:"bottleID"`
        // Category
        Category string `form:"category" json:"category" xml:"category"`
        // Comment
        Comment *string `form:"comment,omitempty" json:"comment,omitempty" xml:"comment,omitempty"` // ← こいつがポインタあつかい
}

必須要素にしないときの振る舞いは?

Payload で要素を必須要素にしない場合,その要素は省略できます. つまり,json で渡そうと思ったとき,

{ "bottleID" : 1, "category": "white" }

の様に(省略可能な) comment は書かなくても大丈夫です.必須要素の bottleID を省略すると,goa が生成するバリデーションではねられます.

MediaType の場合は required でなければ,ゼロ値であればレスポンスから項目が省略されます.

必須要素でも記述を省略することは可能

大体問題になるのは,Payload の要素で省略したいものがある場合だと思います. しかし,単に記述を省略したいのでしたら,必須要素にして Default DSL を利用することで,あたかも要素を省略したように記述できます.

BottlePayload = Type("BottlePayload", func() {
        Member("bottleID", Integer, "Bottle ID", func(){
                Minimum(0)
                Maximum(127)
        })
        Member("category", String, "Category", func(){
                Enum("red", "whilte", "rose")
                Default("red")
        })
        Member("comment", String, "Comment", func(){
                Default("")                       // ← ★
                MaxLength(256)
        })
        Required("bottleID", "category", "comment") // ← 必須要素の指定
})

こうしておくと,comment の値の初期値は空文字列になります.なので,

{ "bottleID" : 1, "category": "white" }

という Payload を渡しても,初期値が設定されるので,required の要件を満たします(詭弁ぽいけど). なので,初期値を設定できる要素であれば,必須要素にしておいて,Default を指定することで記述は省略可能になります.

ただし,null と空文字列を区別したいような場合にはこの方法は出来ません.

逆に言えば,null と初期値値を区別したいような場合だけ,必須要素でなくす必要がある・・・という方針でとりあえずうまくいっています.

といっても,これは goa でポインタ使いたくないと云うためのものぐさな手法で, API の作法的にどうなのかと常々疑問に思っているので,何か知見がある方いらしたらコメントいただければ嬉しいです.