Skip to main content
Version: 2.8.x(Latest)

According to the RESTful style, adding should use the POST method. Here comes our three-point development approach.

Add Api


api/words/v1/words.go

type CreateReq struct {  
g.Meta `path:"words" method:"post" sm:"Create" tags:"Word"`
Word string `json:"word" v:"required|length:1,100" dc:"Word"`
Definition string `json:"definition" v:"required|length:1,300" dc:"Definition"`
ExampleSentence string `json:"example_sentence" v:"required|length:1,300" dc:"Example Sentence"`
ChineseTranslation string `json:"chinese_translation" v:"required|length:1,300" dc:"Chinese Translation"`
Pronunciation string `json:"pronunciation" v:"required|length:1,100" dc:"Pronunciation"`
ProficiencyLevel uint `json:"proficiency_level" v:"required|between:1,5" dc:"Proficiency Level, 1 is lowest, 5 is highest"`
}

type CreateRes struct {
}

Did you notice that the CreateReq structure is very similar to the previously defined model.WordInput? Can we reuse it to keep the api and logic consistent and streamline the code? Like this:

api/words/v1/words.go

type CreateReq struct {  
g.Meta `path:"words" method:"post" sm:"Create" tags:"Word"`
model.WordInput
}

...

internal/model/words.go

package model  

...

type WordInput struct {
Word string `json:"word" v:"required|length:1,100" dc:"Word"`
Definition string `json:"definition" v:"required|length:1,300" dc:"Definition"`
ExampleSentence string `json:"example_sentence" v:"required|length:1,300" dc:"Example Sentence"`
ChineseTranslation string `json:"chinese_translation" v:"required|length:1,300" dc:"Chinese Translation"`
Pronunciation string `json:"pronunciation" v:"required|length:1,100" dc:"Pronunciation"`
ProficiencyLevel uint `json:"proficiency_level" v:"required|between:1,5" dc:"Proficiency Level, 1 is lowest, 5 is highest"`
}

The answer is that the program runs normally, but this approach is highly inadvisable. This is because the Api layer is the data reception layer, and the Logic layer is the logical operation layer. This method of passthrough will bring the following problems:

  • Method parameter definitions are unclear, leading to additional collaboration costs and risks of ambiguity.
  • The same data structure is coupled with multiple methods, any change in the data structure will affect all related methods.
  • Related methods cannot be fully reused.

The best practice is to write more lines of code rather than passing through data models.

Write Logic


internal/logic/words/words.go

package words  

import (
"context"

"github.com/gogf/gf/v2/errors/gerror"
"star/internal/dao"
"star/internal/model"
"star/internal/model/do"
)

func Create(ctx context.Context, in *model.WordInput) error {
if err := checkWord(ctx, in); err != nil {
return err
}

_, err := dao.Words.Ctx(ctx).Data(do.Words{
Uid: in.Uid,
Word: in.Word,
Definition: in.Definition,
ExampleSentence: in.ExampleSentence,
ChineseTranslation: in.ChineseTranslation,
Pronunciation: in.Pronunciation,
ProficiencyLevel: in.ProficiencyLevel,
}).Insert()
if err != nil {
return err
}
return nil
}

func checkWord(ctx context.Context, in *model.WordInput) error {
count, err := dao.Words.Ctx(ctx).Where("uid", in.Uid).Where("word", in.Word).Count()
if err != nil {
return err
}
if count > 0 {
return gerror.New("Word already exists")
}
return nil
}

In the Logic layer, we also need to ensure that the same user's words cannot be duplicated, keeping consistency with the database.

Controller Calls Logic


The word table contains a uid field, and we need to encapsulate a GetUid function in the logic/users package to provide uid.

internal/logic/users/account.go

func GetUid(ctx context.Context) (uint, error) {  
user, err := Info(ctx)
if err != nil {
return 0, err
}
return user.Id, nil
}

internal/controller/words/words_v1_create.go

package words  

import (
"context"

"star/internal/logic/users"
"star/internal/logic/words"
"star/internal/model"
"star/api/words/v1"
)

func (c *ControllerV1) Create(ctx context.Context, req *v1.CreateReq) (res *v1.CreateRes, err error) {
uid, err := users.GetUid(ctx)
if err != nil {
return nil, err
}
err = words.Create(ctx, &model.WordInput{
Uid: uid,
Word: req.Word,
Definition: req.Definition,
ExampleSentence: req.ExampleSentence,
ChineseTranslation: req.ChineseTranslation,
Pronunciation: req.Pronunciation,
ProficiencyLevel: model.ProficiencyLevel(req.ProficiencyLevel),
})
return nil, err
}

In the Controller, two Logic layer functions are called: users.GetUid and words.Create to implement the functionality. Note, do not directly call users.GetUid in words.Create, deleting users.GetUid from the Controller will increase the coupling of the words package.

The best practice is to ensure that the functionality of Logic functions is single, calling Logic multiple times in the Controller to achieve functionality.

Register Controller


internal/cmd/cmd.go

package cmd  

...

var (
Main = gcmd.Command{
Name: "main",
Usage: "main",
Brief: "start http server",
Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
s := g.Server()
s.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(ghttp.MiddlewareHandlerResponse)
group.Group("/v1", func(group *ghttp.RouterGroup) {
group.Bind(
users.NewV1(),
)
group.Group("/", func(group *ghttp.RouterGroup) {
group.Middleware(middleware.Auth)
group.Bind(
account.NewV1(),
words.NewV1(),
)
})
})
})
s.Run()
return nil
},
}
)

The controller is registered under the same route group as account.NewV1() to ensure it can pass through the Auth middleware.

Interface Testing


$ curl -X POST http://127.0.0.1:8000/v1/words \
-H "Authorization: eyJhbGci...5U" \
-H "Content-Type: application/json" \
-d '{
"word": "example",
"definition": "A representative form or pattern.",
"example_sentence": "This is an example sentence.",
"chinese_translation": "例子",
"pronunciation": "ɪɡˈzɑːmp(ə)l",
"proficiency_level": 3
}'

{
    "code": 0,
    "message": "",
    "data": null
}

Execute the command to check if the data is added correctly:

$ SELECT * FROM words;
iduidworddefinitionexample_sentencechinese_translationpronunciationproficiency_levelcreated_atupdated_at
11exampleA representative form or pattern.This is an example sentence.例子ɪɡˈzɑːmp(ə)l32024/11/12 15:38:502024/11/12 15:38:50