feat: create session. (also print the x-rid into the log)
This commit is contained in:
parent
be7f57d5a1
commit
a3c2ade9fb
54
README.md
54
README.md
@ -102,10 +102,10 @@ The execution of the program is then just a command like `howmuch run`.
|
||||
Moreover, in a distributed system, configs can be stored on `etcd`.
|
||||
|
||||
> [Kubernetes stores configuration data into etcd for service discovery and
|
||||
cluster management; etcd’s consistency is crucial for correctly scheduling
|
||||
and operating services. The Kubernetes API server persists cluster state
|
||||
into etcd. It uses etcd’s watch API to monitor the cluster and roll out
|
||||
critical configuration changes.](https://etcd.io/docs/v3.5/learning/why/)
|
||||
> cluster management; etcd’s consistency is crucial for correctly scheduling
|
||||
> and operating services. The Kubernetes API server persists cluster state
|
||||
> into etcd. It uses etcd’s watch API to monitor the cluster and roll out
|
||||
> critical configuration changes.](https://etcd.io/docs/v3.5/learning/why/)
|
||||
|
||||
#### Business logic
|
||||
|
||||
@ -113,8 +113,8 @@ critical configuration changes.](https://etcd.io/docs/v3.5/learning/why/)
|
||||
- init DBs (Redis, SQL, Kafka, etc.)
|
||||
- init web service (http, https, gRPC, etc.)
|
||||
- start async tasks like `watch kube-apiserver`; pull data from third-party
|
||||
services; store, register `/metrics` and listen on some port; start kafka
|
||||
consumer queue, etc.
|
||||
services; store, register `/metrics` and listen on some port; start kafka
|
||||
consumer queue, etc.
|
||||
- Run specific business logic
|
||||
- Stop the program
|
||||
- others...
|
||||
@ -166,26 +166,26 @@ that has several layers:
|
||||
- Entities: the models of the product
|
||||
- Use cases: the core business rule
|
||||
- Interface Adapters: convert data-in to entities and convert data-out to
|
||||
output ports.
|
||||
output ports.
|
||||
- Frameworks and drivers: Web server, DB.
|
||||
|
||||
Based on this logic, we create the following directories:
|
||||
|
||||
- `model`: entities
|
||||
- `infra`: Provides the necessary functions to setup the infrastructure,
|
||||
especially the DB (output-port), but also the router (input-port). Once
|
||||
setup, we don't touch them anymore.
|
||||
especially the DB (output-port), but also the router (input-port). Once
|
||||
setup, we don't touch them anymore.
|
||||
- `registry`: Provides a register function for the main to register a service.
|
||||
It takes the pass to the output-port (ex.DBs) and gives back a pass
|
||||
(controller) to the input-port
|
||||
It takes the pass to the output-port (ex.DBs) and gives back a pass
|
||||
(controller) to the input-port
|
||||
- `adapter`: Controllers are one of the adapters, when they are called,
|
||||
they parse the user input and parse them into models and run the usecase
|
||||
rules. Then they send back the response(input-port). For the output-port
|
||||
part, the `repo` is the implementation of interfaces defined in `usecase/repo`.
|
||||
they parse the user input and parse them into models and run the usecase
|
||||
rules. Then they send back the response(input-port). For the output-port
|
||||
part, the `repo` is the implementation of interfaces defined in `usecase/repo`.
|
||||
- `usecase`: with the input of adapter, do what have to be done, and answer
|
||||
with the result. In the meantime, we may have to store things into DBs.
|
||||
Here we use the Repository model to decouple the implementation of the repo
|
||||
with the interface. Thus in `usecase/repo` we only define interfaces.
|
||||
with the result. In the meantime, we may have to store things into DBs.
|
||||
Here we use the Repository model to decouple the implementation of the repo
|
||||
with the interface. Thus in `usecase/repo` we only define interfaces.
|
||||
|
||||
Then it comes the real design for the app.
|
||||
|
||||
@ -229,7 +229,7 @@ For the test-driven part,
|
||||
(success, timeout, etc.). To test, we have to mock the
|
||||
**database connection**.
|
||||
- usecase: This is the core part to test, it's about the core business.
|
||||
We provide the data input and we check the data output in a fake repository.
|
||||
We provide the data input and we check the data output in a fake repository.
|
||||
|
||||
With this design, although it may seem overkill for this little project, fits
|
||||
perfectly well with the TDD method.
|
||||
@ -253,7 +253,7 @@ integration test part (the 2nd point).
|
||||
|
||||
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*.
|
||||
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,
|
||||
@ -292,8 +292,8 @@ 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.
|
||||
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,
|
||||
@ -330,3 +330,15 @@ the database model design.
|
||||
![Core user story part 1](./docs/howmuch_us1.drawio.png)
|
||||
|
||||
![Database model](./docs/howmuch.drawio.png)
|
||||
|
||||
### 2024/10/11
|
||||
|
||||
I spent 2 days learning some basic of Vue. Learning Vue takes time. There
|
||||
are a lot of concepts and it needs a lot of practice. Even though I may not
|
||||
need a professional level web page, I don't want to copy one module from this
|
||||
blog and another one from another tutorial. I might just put aside the
|
||||
front-end for now and concentrate on my backend Go app.
|
||||
|
||||
For now, I will just test my backend with `curl`.
|
||||
|
||||
And today's job is to get the login part done!
|
||||
|
@ -28,4 +28,6 @@ type AppController struct {
|
||||
User interface{ User }
|
||||
|
||||
Admin interface{ Admin }
|
||||
|
||||
Session interface{ Session }
|
||||
}
|
||||
|
20
internal/howmuch/adapter/controller/session.go
Normal file
20
internal/howmuch/adapter/controller/session.go
Normal file
@ -0,0 +1,20 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/log"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type Session interface {
|
||||
Create(*gin.Context)
|
||||
}
|
||||
|
||||
type SessionController struct{}
|
||||
|
||||
func NewSessionController() Session {
|
||||
return &SessionController{}
|
||||
}
|
||||
|
||||
func (sc *SessionController) Create(ctx *gin.Context) {
|
||||
log.CtxLog(ctx).DebugLog("session create")
|
||||
}
|
@ -54,6 +54,8 @@ func Routes(engine *gin.Engine, c controller.AppController) *gin.Engine {
|
||||
{
|
||||
userV1.POST("/create", func(ctx *gin.Context) { c.User.Create(ctx) })
|
||||
}
|
||||
|
||||
v1.POST("/session/create", func(ctx *gin.Context) { c.Session.Create(ctx) })
|
||||
}
|
||||
|
||||
return engine
|
||||
|
@ -53,5 +53,6 @@ func (r *registry) NewAppController() controller.AppController {
|
||||
return controller.AppController{
|
||||
User: r.NewUserController(),
|
||||
Admin: r.NewAdminController(),
|
||||
Session: r.NewSessionController(),
|
||||
}
|
||||
}
|
||||
|
9
internal/howmuch/registry/session.go
Normal file
9
internal/howmuch/registry/session.go
Normal file
@ -0,0 +1,9 @@
|
||||
package registry
|
||||
|
||||
import "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/controller"
|
||||
|
||||
// NewSessionController returns a session controller's implementation
|
||||
func (r *registry) NewSessionController() controller.Session {
|
||||
// u := usecase.NewSessionUsecase(repo.NewSessionRepository(r.db), repo.NewDBRepository(r.db))
|
||||
return controller.NewSessionController()
|
||||
}
|
@ -33,6 +33,7 @@ type Context interface {
|
||||
|
||||
// Request
|
||||
Bind(obj any) error
|
||||
GetHeader(key string) string
|
||||
|
||||
// Response
|
||||
JSON(code int, obj any)
|
||||
|
@ -26,6 +26,8 @@ import (
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/core"
|
||||
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/middleware"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
@ -100,6 +102,26 @@ func NewLogger(opts *Options) *zapLogger {
|
||||
return &zapLogger{z: z}
|
||||
}
|
||||
|
||||
// CtxLog writes context's information into the log
|
||||
func CtxLog(ctx core.Context) *zapLogger {
|
||||
return std.CtxLog(ctx)
|
||||
}
|
||||
|
||||
func (z *zapLogger) CtxLog(ctx core.Context) *zapLogger {
|
||||
zz := z.clone()
|
||||
|
||||
if rid := ctx.GetHeader(middleware.XRequestID); rid != "" {
|
||||
zz.z = zz.z.With(zap.Any(middleware.XRequestID, rid))
|
||||
}
|
||||
|
||||
return zz
|
||||
}
|
||||
|
||||
func (z *zapLogger) clone() *zapLogger {
|
||||
zz := *z
|
||||
return &zz
|
||||
}
|
||||
|
||||
func (z *zapLogger) FatalLog(msg string, keyValues ...interface{}) {
|
||||
z.z.Sugar().Fatalw(msg, keyValues...)
|
||||
}
|
||||
|
@ -27,20 +27,20 @@ import (
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
const requestID = "X-Request-Id"
|
||||
const XRequestID = "X-Request-Id"
|
||||
|
||||
func RequestID() gin.HandlerFunc {
|
||||
return func(ctx *gin.Context) {
|
||||
var rid string
|
||||
|
||||
if rid = ctx.GetHeader(requestID); rid != "" {
|
||||
ctx.Request.Header.Add(requestID, rid)
|
||||
if rid = ctx.GetHeader(XRequestID); rid != "" {
|
||||
ctx.Request.Header.Add(XRequestID, rid)
|
||||
ctx.Next()
|
||||
}
|
||||
|
||||
rid = uuid.NewString()
|
||||
ctx.Request.Header.Add(requestID, rid)
|
||||
ctx.Header(requestID, rid)
|
||||
ctx.Request.Header.Add(XRequestID, rid)
|
||||
ctx.Header(XRequestID, rid)
|
||||
ctx.Next()
|
||||
}
|
||||
}
|
||||
|
@ -58,21 +58,21 @@ func TestRequestID(t *testing.T) {
|
||||
wanted := "123"
|
||||
|
||||
r.GET("/example", func(c *gin.Context) {
|
||||
got = c.GetHeader(requestID)
|
||||
got = c.GetHeader(XRequestID)
|
||||
c.Status(http.StatusOK)
|
||||
})
|
||||
|
||||
r.POST("/example", func(c *gin.Context) {
|
||||
got = c.GetHeader(requestID)
|
||||
got = c.GetHeader(XRequestID)
|
||||
c.String(http.StatusAccepted, "ok")
|
||||
})
|
||||
|
||||
// Test with Request ID
|
||||
_ = performRequest(r, "GET", "/example?a=100", header{requestID, wanted})
|
||||
_ = performRequest(r, "GET", "/example?a=100", header{XRequestID, wanted})
|
||||
assert.Equal(t, "123", got)
|
||||
|
||||
res := performRequest(r, "GET", "/example?a=100")
|
||||
assert.NotEqual(t, "", got)
|
||||
assert.NoError(t, uuid.Validate(got))
|
||||
assert.Equal(t, res.Header()[requestID][0], got)
|
||||
assert.Equal(t, res.Header()[XRequestID][0], got)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user