fix: change API design to be more RESTful
This commit is contained in:
parent
c1173b4bcc
commit
cb7a4bf5c5
69
README.md
69
README.md
@ -13,6 +13,9 @@
|
|||||||
- [Version](#version)
|
- [Version](#version)
|
||||||
- [2024/10/03](#20241003)
|
- [2024/10/03](#20241003)
|
||||||
- [2024/10/04](#20241004)
|
- [2024/10/04](#20241004)
|
||||||
|
- [2024/10/06](#20241006)
|
||||||
|
- [Workflow](#workflow)
|
||||||
|
- [2024/10/07](#20241007)
|
||||||
<!--toc:end-->
|
<!--toc:end-->
|
||||||
|
|
||||||
A tricount like expense-sharing system written in Go
|
A tricount like expense-sharing system written in Go
|
||||||
@ -241,3 +244,69 @@ rest, I just put unit tests aside for later.
|
|||||||
That should be the correct workflow. But to save time, I will cut off the
|
That should be the correct workflow. But to save time, I will cut off the
|
||||||
integration test part (the 2nd point).
|
integration test part (the 2nd point).
|
||||||
|
|
||||||
|
### 2024/10/07
|
||||||
|
|
||||||
|
I rethought about the whole API design (even though I have only one yet). I
|
||||||
|
have created `/signup` and `/login` without thinking too much, but in fact
|
||||||
|
it is not quite *RESTful*.
|
||||||
|
|
||||||
|
**REST** is all about resources. While `/signup` and `/login` is quite
|
||||||
|
comprehensible, thus service-oriented, they don't follow the REST philosophy,
|
||||||
|
that is to say, **resource-oriented**.
|
||||||
|
|
||||||
|
If we rethink about `/signup`, what it does is to create a resource of `User`.
|
||||||
|
Thus, for a backend API, it'd better be named as `User.Create`. But what
|
||||||
|
about `/login`, it doesn't do anything about `User`. It would be strange to
|
||||||
|
declare it as a User-relevant method.
|
||||||
|
|
||||||
|
Instead, what `/login` really does, is to **create a session**.
|
||||||
|
In consequence, we have to create a new struct `Session` that can be created,
|
||||||
|
deleted, or updated.
|
||||||
|
|
||||||
|
It might seem overkill, and in real life, even in the official Pet store
|
||||||
|
example of OpenAPI, signup and login are under /user. But it just opened my
|
||||||
|
mind and forces me to **think and design RESTfully**!
|
||||||
|
|
||||||
|
That being said, for the user side, we shall still have `/signup` and `/login`,
|
||||||
|
because on the Front-end, we must be user-centered. We can even make this
|
||||||
|
2 functions on the same page with the same endpoint `/login`. The user enter
|
||||||
|
the email and the password, then clicks on `Login or Signup`. If the login
|
||||||
|
is successful, then he is logged in. Otherwise, if the user doesn't exist
|
||||||
|
yet, we open up 2 more inputs (first name and last name) for signup. They
|
||||||
|
can just provide the extra information and click again on `Signup`.
|
||||||
|
|
||||||
|
That, again, being said, I am thinking about doing some Front-end stuff just
|
||||||
|
to make the validation tests of the product simpler.
|
||||||
|
|
||||||
|
#### The choice of the front end framework
|
||||||
|
|
||||||
|
I have considered several choices.
|
||||||
|
|
||||||
|
If I didn't purposely make the backend code to provide a REST API, I might
|
||||||
|
choose server-side-rendering with `templ + htmx`, or even `template+vanilla
|
||||||
|
javascript`.
|
||||||
|
|
||||||
|
I can still write a rather static Go-frontend-server to serve HTMLs and call
|
||||||
|
my Go backend. *And it might be a good idea if they communicate on Go native
|
||||||
|
rpc.* It worth a try.
|
||||||
|
|
||||||
|
And I have moved on to `Svelte` which seems very simple by design and the
|
||||||
|
whole compile thing makes it really charm. But this is mainly a Go project,
|
||||||
|
to learn something new with a rather small community means potentially more
|
||||||
|
investment. I can learn it later.
|
||||||
|
|
||||||
|
Among `Angular`, `React` and `Vue`, I prefer `Vue`, for several reasons.
|
||||||
|
First, `Angular` is clearly overkill for this small demo project. Second,
|
||||||
|
`React` is good but I personally like the way of Vue doing things. And I
|
||||||
|
work with Vue at work, so I might have more technical help from my colleagues.
|
||||||
|
|
||||||
|
So the plan for this week is to have both the Front end part and Backend part
|
||||||
|
working, just for user signup and login.
|
||||||
|
|
||||||
|
I would like to directly put this stuff on a CI-pipeline for tests and
|
||||||
|
deployment, even I have barely nothing yet. It is always good to do this
|
||||||
|
preparation stuff at the early stage of the project. So we can benefit from
|
||||||
|
them all the way along.
|
||||||
|
|
||||||
|
Moreover, even I am not really finishing the project, it can still be
|
||||||
|
something representable that I can show to a future interviewer.
|
||||||
|
@ -37,28 +37,79 @@ servers:
|
|||||||
- url: https:/localhost:8000/v1
|
- url: https:/localhost:8000/v1
|
||||||
tags:
|
tags:
|
||||||
- name: user
|
- name: user
|
||||||
|
- name: session
|
||||||
|
|
||||||
paths:
|
paths:
|
||||||
/user/signup:
|
/user/create:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- user
|
- user
|
||||||
description: Sign up as a new user
|
description: Create a new user
|
||||||
requestBody:
|
requestBody:
|
||||||
description: Sign up
|
description: Create a new user
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/UserSignUpRequest'
|
$ref: '#/components/schemas/UserCreateRequest'
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Successful operation
|
description: Successful operation
|
||||||
'400':
|
'400':
|
||||||
description: Client side error
|
description: Client side error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
example: FailedOperation.UserExisted
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
example: "Email already existed."
|
||||||
|
'500':
|
||||||
|
description: Server side error
|
||||||
content:
|
content:
|
||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '#/components/schemas/ErrResponse'
|
$ref: '#/components/schemas/ErrResponse'
|
||||||
|
/session/create:
|
||||||
|
post:
|
||||||
|
tags:
|
||||||
|
- user
|
||||||
|
description: Create a new session for a user
|
||||||
|
requestBody:
|
||||||
|
description: Create session
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/SessionCreateRequest'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful operation
|
||||||
|
headers:
|
||||||
|
X-Expires-After:
|
||||||
|
description: date in UTC when token expires
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
'400':
|
||||||
|
description: Client side error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
example: AuthFailure
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
example: "wrong email password."
|
||||||
'500':
|
'500':
|
||||||
description: Server side error
|
description: Server side error
|
||||||
content:
|
content:
|
||||||
@ -68,7 +119,7 @@ paths:
|
|||||||
|
|
||||||
components:
|
components:
|
||||||
schemas:
|
schemas:
|
||||||
UserSignUpRequest:
|
UserCreateRequest:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
email:
|
email:
|
||||||
@ -88,12 +139,24 @@ components:
|
|||||||
- fist_name
|
- fist_name
|
||||||
- last_name
|
- last_name
|
||||||
- password
|
- password
|
||||||
|
SessionCreateRequest:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
example: bruce@wayne.com
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
example: verystrongpassword
|
||||||
|
required:
|
||||||
|
- email
|
||||||
|
- password
|
||||||
ErrResponse:
|
ErrResponse:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
code:
|
code:
|
||||||
type: string
|
type: string
|
||||||
example: FailedOperation.UserExisted
|
example: InternalError
|
||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
example: "Email already existed."
|
example: "Server internal error."
|
||||||
|
@ -36,10 +36,8 @@ import (
|
|||||||
// User is the user controller interface, it describes all the handlers
|
// User is the user controller interface, it describes all the handlers
|
||||||
// that need to be implemented for the /user endpoint
|
// that need to be implemented for the /user endpoint
|
||||||
type User interface {
|
type User interface {
|
||||||
Signup(core.Context)
|
Create(core.Context)
|
||||||
UpdateInfo(*gin.Context)
|
UpdateInfo(*gin.Context)
|
||||||
Login(*gin.Context)
|
|
||||||
Logout(*gin.Context)
|
|
||||||
ChangePassword(*gin.Context)
|
ChangePassword(*gin.Context)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,7 +57,7 @@ func NewUserController(us usecase.User) User {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *UserController) Signup(ctx core.Context) {
|
func (uc *UserController) Create(ctx core.Context) {
|
||||||
var params model.User
|
var params model.User
|
||||||
|
|
||||||
if err := ctx.Bind(¶ms); err != nil {
|
if err := ctx.Bind(¶ms); err != nil {
|
||||||
@ -87,11 +85,5 @@ func (uc *UserController) Signup(ctx core.Context) {
|
|||||||
func (uc *UserController) UpdateInfo(ctx *gin.Context) {
|
func (uc *UserController) UpdateInfo(ctx *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *UserController) Login(ctx *gin.Context) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uc *UserController) Logout(ctx *gin.Context) {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (uc *UserController) ChangePassword(ctx *gin.Context) {
|
func (uc *UserController) ChangePassword(ctx *gin.Context) {
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ func Routes(engine *gin.Engine, c controller.AppController) *gin.Engine {
|
|||||||
{
|
{
|
||||||
userV1 := v1.Group("/user")
|
userV1 := v1.Group("/user")
|
||||||
{
|
{
|
||||||
userV1.POST("/signup", func(ctx *gin.Context) { c.User.Signup(ctx) })
|
userV1.POST("/create", func(ctx *gin.Context) { c.User.Create(ctx) })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user