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;
id | uid | word | definition | example_sentence | chinese_translation | pronunciation | proficiency_level | created_at | updated_at |
---|---|---|---|---|---|---|---|---|---|
1 | 1 | example | A representative form or pattern. | This is an example sentence. | 例子 | ɪɡˈzɑːmp(ə)l | 3 | 2024/11/12 15:38:50 | 2024/11/12 15:38:50 |