-
Notifications
You must be signed in to change notification settings - Fork 47
03. Week 3 Keynotes
Ngoài các yếu tố về performance, simple, security, thì mình đánh giá một framework phải có một cộng đồng hỗ trợ mạnh với hệ sinh thái về plugin và giải pháp đa dạng.
Session Cookie HTTP Request, Method, Body, Querystring, URI(max length)
Create an note
service to:
type Note struct {
gorm.Model
Title string
Completed bool
}
- Create a note
- Find a note
- Update a note
- Delete a note
- List notes
Do you know about POST/GET?
- Querystring
- Request Body
- Sercurity
See more discussion here: What is the difference between POST and GET?
$ curl http://localhost:8081/note -d '{"title":"Todo 1", "completed": false}' | jq .
{
"ID": 1,
"CreatedAt": "2019-03-30T00:03:28.200475675+07:00",
"UpdatedAt": "2019-03-30T00:03:28.200475675+07:00",
"DeletedAt": null,
"Title": "",
"Completed": false
}
$ curl http://localhost:8081/note/1 | jq .
{
"ID": 1,
"CreatedAt": "2019-03-30T00:03:28.200475675+07:00",
"UpdatedAt": "2019-03-30T00:03:28.200475675+07:00",
"DeletedAt": null,
"Title": "",
"Completed": false
}
func ASimpleMiddleware(c *gin.Context) {
if true {
c.AbortWithStatus(400)
return
}
fmt.Println("Print here for every request")
c.Next()
}
- Middleware nó là một stack
- Middleware trước nó có thể cancel/abort những middleware sau đó.
- Mình có thể sử dụng để tạo ra các giá trị dùng chung cho từng route
Do you exercise: signin/login/jwt authentication
You should do this as your exercise
You should do this as your exercise
You should do this as your exercise
$ docker run -i loadimpact/k6 run --vus 2 -d 5s - <script.js
Test vấn đề Atomic với bài toán counting tăng dần, lệch một lượng so với mong đợi:
✗ status was 200
↳ 0% — ✓ 0 / ✗ 14218
✗ transaction time OK
↳ 99% — ✓ 14132 / ✗ 86
checks.....................: 49.69% ✓ 14122 ✗ 14294
data_received..............: 2.0 MB 407 kB/s
data_sent..................: 1.3 MB 270 kB/s
http_req_blocked...........: avg=9.08µs min=950ns med=3.2µs max=5.07ms p(90)=5.23µs p(95)=7.69µs
http_req_connecting........: avg=2.69µs min=0s med=0s max=4.58ms p(90)=0s p(95)=0s
http_req_duration..........: avg=2.47ms min=406.18µs med=2.18ms max=34.09ms p(90)=4.1ms p(95)=5ms
http_req_receiving.........: avg=44.33µs min=4.81µs med=17.04µs max=20.72ms p(90)=65.36µs p(95)=116.84µs
http_req_sending...........: avg=43.85µs min=5.11µs med=16.21µs max=14.21ms p(90)=56.21µs p(95)=98.45µs
http_req_tls_handshaking...: avg=0s min=0s med=0s max=0s p(90)=0s p(95)=0s
http_req_waiting...........: avg=2.38ms min=376.17µs med=2.11ms max=24.75ms p(90)=3.99ms p(95)=4.84ms
http_reqs..................: 14210 2841.344056/s
iteration_duration.........: avg=2.8ms min=514.38µs med=2.45ms max=35.14ms p(90)=4.53ms p(95)=5.58ms
iterations.................: 14208 2840.944148/s
vus........................: 10 min=10 max=10
vus_max....................: 10 min=10 max=10
Từ k6 test ta mong đợi counter sẽ có giá trị là: 14208
, nhưng thực tế chạy được chỉ là 14132 - 1
, khi chúng ta gọi hàm tiếp theo.
Các bạn tham khảo file
k6/script.js
và hàmpingHandler
var counter int = 0
func pingHandler(c *gin.Context) {
counter += 1
// Race condition => Hai CPU cung access vo 1 cai bien
// Atomic => co 100 request vao nhung expected: counter = 100, actual: 80
c.Writer.Header().Set("X-Counter", strconv.Itoa(counter))
c.String(201, "Pong\n")
}
- Unit Test/Mock
- Validation
- Authentication/Identity Context
Với cách design như bài học, chúng ta cần viết test cho handler NoteCreate
, chỗ này cần đến một khái niệm mock test
Bạn cần xài thư viện github.com/stretchr/testify/mock
để tạo mock object cho việc xử lý db
Chúng ta sẽ cần implement lại NoteRepoImpl
dưới dạng mock, chúng khá đơn giản như sau, nếu như bạn hiểu khái niệm.
Chúng ta có 4 tình huống (test case) cần phải test.
- title is required
- title has min length = 6
- title has max length = 100
- title is valid
import (
"../helper"
"../model"
"github.com/stretchr/testify/mock"
)
type NoteRepoImpl struct {
mock.Mock
}
func (self *NoteRepoImpl) Create(note model.Note) (*model.Note, error) {
args := self.Called(note)
if args.Get(0) == nil {
return nil, args.Error(1)
}
r := args.Get(0).(model.Note)
return &r, args.Error(1)
}
// ... cac function khac nua
Sau khi có mock rồi, test hàm NoteCreate
đơn giản sẽ được viết như sau:
Chú ý, bạn sẽ cần cả việc tìm hiểu thêm về việc giả lập
gin.Context
Thêm nữa là, việc phải giả lập gin context cho thấy việc design phụ thuộc vô
gin.Context
cũng cũng là một việc làm cần phải suy nghĩ về việc liệu nó đã là tốt chưa. Đôi khi chúng ta phảitrade off
. Nhưng bạn có gom lại các dòng code về việc tạogin.Context
vào một function vềtestutil
sẽ giúp cho code test sẽ trong sáng hơn về chỗ này.
func TestNoteCreateWithInValidValidator(t *testing.T) {
gin.SetMode(gin.ReleaseMode)
// 1. Gia lap context
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
data := `{"title":"todo"}`
c.Request = httptest.NewRequest("POST", "/note", strings.NewReader(data))
c.Request.Header.Set("Content-Type", "application/json")
noteRepo := new(m.NoteRepoImpl)
_, err := NoteCreate(c, noteRepo)
if err == nil {
t.Error("Error should not be nil because this will not valid min rule validator")
}
}
func TestNoteCreateWithValidValidator(t *testing.T) {
// 1. Gia lap context
gin.SetMode(gin.ReleaseMode)
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
data := `{"title":"todo 123"}`
c.Request = httptest.NewRequest("POST", "/note", strings.NewReader(data))
c.Request.Header.Set("Content-Type", "application/json")
// 2. Logic nay boc lo code thiet ke khong dung
// 2.1 Do phai dung json.Unmarshal de thanh gia tri truyen vao ham On("Create")
noteRepo := new(m.NoteRepoImpl)
note := model.Note{}
json.Unmarshal([]byte(data), ¬e)
actual := model.Note{
Title: "todo 123",
Model: gorm.Model{
ID: 123,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
},
}
noteRepo.On("Create", note).Return(actual, nil)
expected, err := NoteCreate(c, noteRepo)
if err != nil {
t.Error("Error should be nil")
}
if expected.ID != 123 {
t.Error("note.ID should be 123")
}
}
Ví dụ: title không được empty, min length là: 6, max length là 100
type Note struct {
gorm.Model
Title string `binding:"required,min=6,max=100"`
Completed bool
}
Gin sử dụng https://github.com/go-playground/validator
v8, bạn có tham khảo các syntax khác ở đây.
Câu hỏi khó hơn để suy nghĩ là: làm thế nào để custom error message để thân thiện hơn hoặc là việt hoá
- Tham khảo trong source code về các route như:
/signin
,/login
để biết thêm chi tiết. - Chú ý tới đây các hàm thuộc về note cần có token gởi lên mơi được xử lý
Ví dụ:
- Signin
curl --request POST \
--url http://localhost:8081/signin \
--header 'content-type: application/json' \
--cookie Token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODU5MjgxNDUsImp0aSI6IjQiLCJpc3MiOiJOb3JkaWNDb2RlciJ9.v7nELcSyRcpiKtQqxdxS506T8eKZCcwfZglY7VBGUqo \
--data '{
"username": "tpphu4",
"password": "12345678",
"email": "tpphu4@gmail.com",
"Fullname": "Phu Dep Trai"
}'
- Login
curl --request POST \
--url http://localhost:8081/login \
--header 'content-type: application/json' \
--cookie Token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODU5MjgxNDUsImp0aSI6IjQiLCJpc3MiOiJOb3JkaWNDb2RlciJ9.v7nELcSyRcpiKtQqxdxS506T8eKZCcwfZglY7VBGUqo \
--data '{
"login": "tpphu4",
"password": "12345678"
}'
- Create Note:
curl --request POST \
--url http://localhost:8081/note \
--header 'authentication: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODU5MjgxNDUsImp0aSI6IjQiLCJpc3MiOiJOb3JkaWNDb2RlciJ9.v7nELcSyRcpiKtQqxdxS506T8eKZCcwfZglY7VBGUqo' \
--header 'content-type: application/json' \
--cookie Token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODU5MjgxNDUsImp0aSI6IjQiLCJpc3MiOiJOb3JkaWNDb2RlciJ9.v7nELcSyRcpiKtQqxdxS506T8eKZCcwfZglY7VBGUqo \
--data '{
"title": "The func keyword signifies.",
"completed": false
}'
- Thay cai host docker (chi danh cho Mac)
host.docker.internal
thanh local IP, vi du:192.168.1.xxx
- Cau hinh cai nay
api.dev.local
tro den 127.0.0.1
upstream api {
server host.docker.internal:8081 weight=2;
server host.docker.internal:8082 weight=1;
}
server {
listen 80;
listen [::]:80;
server_name api.dev.local;
location / {
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_set_header Host $http_host;
proxy_pass http://api;
}
}
curl -X POST -H 'Content-Type: application/json' --data '{"Title": "Todo1", "Completed": true}' http://localhost:8088/note | jq .
ⓒ 2019 Phú, Trần Phong & NordicCoder