Closed podhmo closed 2 years ago
In gothinkster/golang-gin-realworld-example-app, handling this mismatch with Validator and Serializer. Simplified definition is here
func ArticleCreate(c *gin.Context) {
// input -> model
articleModelValidator := NewArticleModelValidator()
if err := articleModelValidator.Bind(c); err != nil {
c.JSON(http.StatusUnprocessableEntity, common.NewValidatorError(err))
return
}
// model.Do()
if err := SaveOne(&articleModelValidator.articleModel); err != nil {
c.JSON(http.StatusUnprocessableEntity, common.NewError("database", err))
return
}
// model -> output
serializer := ArticleSerializer{c, articleModelValidator.articleModel}
c.JSON(http.StatusCreated, gin.H{"article": serializer.Response()})
}
definitions .
type ArticleCreateValidator struct {
// has some fields definitions
ArticleModel *ArticleModel
}
func (v *ArticleCreateValidator) Bind(c *gin.Context) error {
// validation and set data to model.
// s.ArticleModel.<something> = <something>
return nil
}
type ArticleCreateSerializer struct {
C *gin.Context
ArticleModel
}
func (s *ArticleCreateSerializer) Response() ArticleCreateResponse {
// serialize model to presentation model (response)
return ArticleCreateResponse{}
}
type ArticleCreateResponse struct {
// ...
}
And path parameter is existed, handling 404. so simplified version is here.
// POST /parent/<parent id>/child (e.g. /articles/<article id>/comments)
func CreateParentChild(c *gin.Context) {
var parent Parent
parentID := c.Param("parentId")
// 404 check from path parameter
if err := FindParent(parentID, &parent); err != nil {
c.JSON(404, common.NewNotFoundError(err))
return
}
// input -> model
var model Child
if err := BindChild(parent, &model); err != nil {
c.JSON(422, common.NewValidationError(err))
return
}
// models's action
var result Result
if err := model.Do(&result); err != nil {
c.JSON(500, common.NewError("DB", err))
return
}
// model -> output
var output Output
if err := SerializeResult(result, &output); err != nil {
c.JSON(500, common.NewError("serialize", err))
return
}
c.JSON(200, output)
}
And, in golang-gin-realworld-example-app, using global variable for handling DB state. So, this model is application model that can access DB in internal method of this one. And in serializer, transform id to data and inject loginUser info from gin's Context.
# with DB data (internal data)
{xId: 10} -> {x: {id: 10, name: "..", ...}}
# with request data (external data)
{data: ..} -> {author: {name: "login user", ...}, data: ...}
RPC
func CreateArticle(db *DB, input Input) (*Output, error) {
return db.Save(input)
}
currently support this version only.
Simply, define action and web-action with transform data.
slim API
handler -> action
fat API
handler -> web-action -> action
But integrate web-action to action is maybe tiresome. (If many many dependencies are existed) using global variable is the one of solutions for this situation (and if so, can skip to use apikit, maybe, but, but ...)
This is nonsense
// How to handle 404?
r.Post("/articles/{articleId}/comments",
CreateArticleComment,
WithBinder(BindArticleComment),
WithSeiralizer(SerializeCreateArticleComment),
)
In a normal DI system, all components (serializers, binders, etc.) are injected using that system, but in apikit, the components are processed in a hand-crafted interface (for example, the provider interface has a GetDB method) to do. That is why, this kind of action is not realistic.
Why can't a wire-like feature solve it?
And how do we handle cases with many more other dependencies? (For example, sending emails, sending messages to queues, notifications, monitoring, etc.)
https://github.com/podhmo/apikit/issues/158#issuecomment-962083461
Currently, this is best.
For example, realworld example's
ArticleCreate()
is fat API https://github.com/gothinkster/golang-gin-realworld-example-app/blob/e4174a9d6450e9e993d9b89a0dace58b7994c045/articles/routers.go#L32-L46this API's openAPI doc is here https://github.com/gothinkster/realworld/blob/f07b0eff21501d020abc369090c9a18b63747eea/api/openapi.yml#L315-L343