Open16

読者コミュニティ|Goa v3 入門

ikawahaikawaha

本の感想や質問をお気軽にコメントしてください。

tama poyotama poyo

大変参考になりました。ありがとうございます。

作っていて疑問に思ったのが、よくある1対多の関係の時にdesignを書いていてinitialization loopになってしまったことです。

下記は商品に複数の口コミ紐付いている例です。

よくあることだとは思うのですが、どうやって避けているのでしょうか?

商品

var ProductResult = ResultType("application/vnd.product+json", func() {
	TypeName("ProductResult")
	Description("商品")
	CreateFrom(result.Product{})
	Attribute("name", String, "商品名")
	Attribute("reviews", ArrayOf(ReviewResult), "口コミリレーション")
	Required("name", "reviews")
	View("default", func() {
		Attribute("name")
		Attribute("reviews")
	})
})

口コミ

var ReviewResult = ResultType("application/vnd.review+json", func() {
	TypeName("ReviewResult")
	Description("口コミ")
	CreateFrom(result.Review{})
	Attribute("text", String, "口コミ内容", func() {
		Example("おいしい!")
	})
	Attribute("product", ProductResult, "商品リレーション", func() {
	})
	Required("text", "product")
	View("default", func() {
		Attribute("text")
		Attribute("product")
	})
})
ikawahaikawaha

コメントいただいた状況を私がうまく理解できているか怪しいですが、商品の定義にレビューが含まれていて、レビューの定義に商品が含まれててしまっているので、定義がループしてしまって initialization loop になってしまうのは確かにそうなのかなと思います。

商品の定義からレビューを外し、レビューには商品の情報も含めるような設計にしておくのがよさそうに思います。

エンドポイントは次のような感じを想定します。

メソッド エンドポイント 得られる情報
GET /products/{id} 指定されて id の商品情報
GET /products/{id}/reviews 指定された id の商品情報に関連するレビュー

レビューを指定する時には商品の情報はもう既知であれば、view でレビューだけを含める場合を default として、extend で商品名も含めたレビューが得られるようにしてもいいかもしれません。

何かご参考になれば幸いです。

package design

import (
	. "goa.design/goa/v3/dsl"
)

var _ = Service("sample", func() {
	Method("show product", func() {
		Payload(func() {
			Attribute("id", Int)
			Required("id")
		})
		Result(ProductResult)
		HTTP(func() {
			GET("/products/{id}")
		})
	})
	Method("show review", func() {
		Payload(func() {
			Attribute( "id", Int)
			Required("id")
		})
		Result(ReviewResult)
		HTTP(func() {
			GET("/products/{id}/reviews")
		})
	})
})

var Product = Type("Product", func() {
	Attribute("name", String, "商品名")
	Required("name")
})

var ProductResult = ResultType("application/vnd.product+json", func() {
	TypeName("ProductResult")
	Description("商品")
	Extend(Product)
	View("default", func() {
		Attribute("name")
	})
})

var Review = Type("Review", func() {
	Attribute("text", String, "口コミ内容", func() {
		Example("おいしい!")
	})
	Attribute("product", Product, "商品リレーション", func() {
	})
	Required("text", "product")
})


var ReviewResult = ResultType("application/vnd.review+json", func() {
	TypeName("ReviewResult")
	Description("口コミ")
	Extend(Review)
	View("default", func() {
		Attribute("text")
	})
	View("extend", func() {
		Attribute("text")
		Attribute("product")
	})

})
tama poyotama poyo

大変参考になりました!ありがとうございます。

なるほどループしないように設計する感じなんですね。

別途ResultTypeを作って対応でもよいのかなあと試行錯誤しておりました。
確かに教えていただいたもののほうが、疎結合で良いなと思いました。

ありがとうございます。

var ProductResult = ResultType("application/vnd.product+json", func() {
	TypeName("ProductResult")
	Description("商品")
	CreateFrom(result.Product{})
	Attribute("name", String, "商品名")
	Required("name", "reviews")
	View("default", func() {
		Attribute("name")
		Attribute("reviews")
	})
})

var ProductWithReviewsResult = ResultType("application/vnd.product_with_reviews+json", func() {
	TypeName("ProductWithReviewsResult")
	Description("商品とそれに紐づく口コミ")
	Attribute("product", ProductResult, "商品名")
	Attribute("reviews", ArrayOf(ReviewResult), "商品に紐づく口コミ")
	Required("product", "reviews")
	View("default", func() {
		Attribute("product")
		Attribute("reviews")
	})
})
ikawahaikawaha

別途 ResultType を用意するのもよいと思います!呼ぶ側の都合もあるので、こういったところは柔軟に設計を変更したいですよね。そういうことが Goa だとデザインを変更するだけで割と簡単に書けて、その影響もサービスメソッド内にとどまるので比較的シュッと変更できる、というのがいいところだと思っています。╭( ・ㅂ・)و ̑̑

ikawahaikawaha

ちょっとした更新:セキュリティの章に JWT の例を追加しました

ikawahaikawaha

記事で書いている「Goaの更新履歴」へのリンクをまとめたページと、HTTPトランスポートでレスポンスに MapOf を含むときの Tips を追加しました

ikawahaikawaha

Goa v3.13.0 で mux router が treemux から chi に変更されて trailing slash の扱いが変わったので、Chapter 22『HTTP: Trailing Slash について知る』の末尾に chi での trailing slash の扱われ方を追記しました。

ikawahaikawaha

Goa v3.13.x で mux router が treemux から chi に変更されて trailing slash の扱いがちょっと混乱していたのですが、v3.14.0 で動作が確定したので、Chapter 22『HTTP: Trailing Slash について知る』の末尾に chi での trailing slash の扱われ方を修正しました。

zhongxuezhongxue

hello i want get the native *http.Request as the payload. what can i do for this?

ikawahaikawaha

Hello,

To get the native *http.Request as the payload, you have a couple of options:

  • You can use the SkipRequestBodyEncodeDecode() DSL to bypass the request body encoding and decoding. This will allow you to work directly with the HTTP request.
  • Alternatively, you could create middleware that captures the original *http.Request and stores it in the context (ctx) for later retrieval and use.

For more detailed questions or to delve deeper into this topic, I recommend asking in the Goa channel on the Gopher Slack (https://gophers.slack.com/messages/goa/). You'll find a community of developers there who can offer extensive guidance on the Goa framework.