### Go Main Application Setup with Spine Framework Source: https://spine.na2ru2.me/ko/reference/examples/login Initializes the Spine application, registers constructors for stores and controllers, configures global interceptors (like CORS), and defines routes for public and protected endpoints. Starts the HTTP server. ```go package main import ( "log" "login-example/controller" "login-example/interceptor" "login-example/store" "github.com/NARUBROWN/spine" "github.com/NARUBROWN/spine/interceptor/cors" "github.com/NARUBROWN/spine/pkg/boot" "github.com/NARUBROWN/spine/pkg/route" ) func main() { app := spine.New() // 생성자 등록 app.Constructor( store.NewUserStore, controller.NewAuthController, ) // 전역 인터셉터 — CORS app.Interceptor( cors.New(cors.Config{ AllowOrigins: []string{"*"}, AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, AllowHeaders: []string{"Content-Type", "Authorization"}, }), ) // 공개 라우트 — 인증 불필요 app.Route("POST", "/signup", (*controller.AuthController).Signup) app.Route("POST", "/login", (*controller.AuthController).Login) // 보호된 라우트 — 인증 필요 (라우트 인터셉터 사용) app.Route( "GET", "/me", (*controller.AuthController).GetMe, route.WithInterceptors(&interceptor.AuthInterceptor{}), ) log.Println("서버 시작: http://localhost:8080") app.Run(boot.Options{ Address: ":8080", EnableGracefulShutdown: true, ShutdownTimeout: 10 * time.Second, HTTP: &boot.HTTPOptions{}, }) } ``` -------------------------------- ### Project Setup with Go Modules Source: https://spine.na2ru2.me/ko/reference/examples/login Initializes a new Go project and sets up the Go module for dependency management. This is a foundational step for any Go project. ```bash mkdir login-example cd login-example go mod init login-example ``` -------------------------------- ### Install Project Dependencies Source: https://spine.na2ru2.me/ko/reference/examples/login Installs necessary Go packages for the project, including the Spine framework and the JWT library for authentication. These are required for building the JWT login functionality. ```bash go get github.com/NARUBROWN/spine go get github.com/golang-jwt/jwt/v5 ``` -------------------------------- ### Example: Get Origin and Authorization Headers Source: https://spine.na2ru2.me/ko/reference/api/execution-context An example showing how to retrieve the 'Origin' and 'Authorization' headers from an HTTP request. ```go origin := ctx.Header("Origin") auth := ctx.Header("Authorization") ``` -------------------------------- ### Bash Command to Run the Go Application Source: https://spine.na2ru2.me/ko/reference/examples/login A simple bash command to compile and run the Go application from the current directory. This is used to start the web server for testing. ```bash go run . ``` -------------------------------- ### Example: Get Path Keys Source: https://spine.na2ru2.me/ko/reference/api/execution-context An example showing how to retrieve the declared path parameter keys from a route definition. ```go // Route: /users/:userId/posts/:postId ctx.PathKeys() // ["userId", "postId"] ``` -------------------------------- ### Example: Access Query Parameters Source: https://spine.na2ru2.me/ko/reference/api/execution-context An example demonstrating how to access query parameters, including handling multiple values for the same key. ```go // Request: /users?status=active&tag=go&tag=web queries := ctx.Queries() // {"status": ["active"], "tag": ["go", "web"]} ``` -------------------------------- ### Run Application with Boot Options Source: https://spine.na2ru2.me/ko/reference/api/spine-app Starts the application, launching the HTTP server and event consumer runtime. It takes boot options as a parameter and returns an error if the server fails to start. ```go func Run(opts boot.Options) error ``` ```go if err := app.Run(boot.Options{ Address: ":8080", HTTP: &boot.HTTPOptions{}, }); err != nil { log.Fatal(err) } ``` ```go app.Run(boot.Options{ Address: ":8080", HTTP: &boot.HTTPOptions{}, }) ``` ```go app.Run(boot.Options{ Address: ":8080", EnableGracefulShutdown: true, ShutdownTimeout: 10 * time.Second, HTTP: &boot.HTTPOptions{ GlobalPrefix: "/api/v1/", }, }) ``` ```go app.Run(boot.Options{ Address: ":8080", Kafka: &boot.KafkaOptions{ Brokers: []string{"localhost:9092"}, Read: &boot.KafkaReadOptions{ GroupID: "my-consumer-group", }, Write: &boot.KafkaWriteOptions{ TopicPrefix: "myapp.", }, HTTP: &boot.HTTPOptions{}, }, }) ``` ```go app.Run(boot.Options{ Address: ":8080", RabbitMQ: &boot.RabbitMqOptions{ URL: "amqp://guest:guest@localhost:5672/", Read: &boot.RabbitMqReadOptions{ Queue: "order.events", Exchange: "events-exchange", RoutingKey: "order.*", }, Write: &boot.RabbitMqWriteOptions{ Exchange: "events-exchange", RoutingKey: "order.created", }, HTTP: &boot.HTTPOptions{}, }, }) ``` -------------------------------- ### Example: Log Request Details Source: https://spine.na2ru2.me/ko/reference/api/execution-context An example demonstrating how to log the request method and path for debugging or monitoring purposes. ```go log.Printf("[REQ] %s %s", ctx.Method(), ctx.Path()) // HTTP: [REQ] GET /users/123 // Consumer: [REQ] EVENT order.created ``` -------------------------------- ### Example: Store Request Data Source: https://spine.na2ru2.me/ko/reference/api/execution-context Examples of using the Set method to store authenticated user information and the request start time within the execution context. ```go ctx.Set("auth.user", authenticatedUser) ctx.Set("request.startTime", time.Now()) ``` -------------------------------- ### Example: Access Route Parameters Source: https://spine.na2ru2.me/ko/reference/api/execution-context An example demonstrating how to access route parameters like 'userId' and 'postId' from a request URL. ```go // Route: /users/:userId/posts/:postId // Request: /users/123/posts/456 params := ctx.Params() // {"userId": "123", "postId": "456"} ``` -------------------------------- ### Example: Handle OPTIONS Request Source: https://spine.na2ru2.me/ko/reference/api/execution-context An example of checking the request method to handle preflight OPTIONS requests, often used in CORS scenarios. ```go func (i *CORSInterceptor) PreHandle(ctx core.ExecutionContext, meta core.HandlerMeta) error { if ctx.Method() == "OPTIONS" { // Preflight 요청 처리 } return nil } ``` -------------------------------- ### Full Spine Application Setup with Services and Interceptors in Go Source: https://spine.na2ru2.me/ko/learn/tutorial/6-transaction This Go code demonstrates a complete Spine application setup. It includes registering constructors for various components (DB, interceptors, repositories, services, controllers), applying interceptors, registering routes, and running the HTTP server with graceful shutdown options. ```go // main.go import ( "time" "spine" "spine/boot" "spine/controller" "spine/interceptor" "spine/repository" "spine/routes" ) func main() { app := spine.New() app.Constructor( NewDB, interceptor.NewTxInterceptor, repository.NewUserRepository, repository.NewOrderRepository, service.NewUserService, service.NewOrderService, controller.NewUserController, controller.NewOrderController, ) app.Interceptor( (*interceptor.TxInterceptor)(nil), &interceptor.LoggingInterceptor{}, ) routes.RegisterUserRoutes(app) routes.RegisterOrderRoutes(app) app.Run(boot.Options{ Address: ":8080", EnableGracefulShutdown: true, ShutdownTimeout: 10 * time.Second, HTTP: &boot.HTTPOptions{}, }) } ``` -------------------------------- ### Install Bun ORM and Database Drivers Source: https://spine.na2ru2.me/ko/learn/tutorial/5-database Installs the Bun ORM core, MySQL driver, and optionally the PostgreSQL driver. These are necessary for database interactions. ```bash # Bun core go get github.com/uptrace/bun # MySQL driver + dialect go get github.com/uptrace/bun/dialect/mysqldialect go get github.com/go-sql-driver/mysql # If using PostgreSQL # go get github.com/uptrace/bun/dialect/pgdialect # go get github.com/jackc/pgx/v5/stdlib ``` -------------------------------- ### PreHandle Method Implementation Example (Go) Source: https://spine.na2ru2.me/ko/reference/api/interceptor An example implementation of the PreHandle method for an authentication interceptor. It checks for an Authorization header, validates the token, and sets user information in the context. Returns an error to halt the pipeline or nil to proceed. ```go func (i *AuthInterceptor) PreHandle(ctx core.ExecutionContext, meta core.HandlerMeta) error { token := ctx.Header("Authorization") if token == "" { return httperr.Unauthorized("인증이 필요합니다") } user, err := i.auth.Validate(token) if err != nil { return httperr.Unauthorized("유효하지 않은 토큰입니다") } ctx.Set("auth.user", user) return nil } ``` -------------------------------- ### PostHandle Method Implementation Example (Go) Source: https://spine.na2ru2.me/ko/reference/api/interceptor An example implementation of the PostHandle method for a logging interceptor. This method is executed after the controller and ReturnValueHandler have finished. It logs the response details. ```go func (i *LoggingInterceptor) PostHandle(ctx core.ExecutionContext, meta core.HandlerMeta) { log.Printf("[RES] %s %s OK", ctx.Method(), ctx.Path()) } ``` -------------------------------- ### User Signup API Source: https://spine.na2ru2.me/ko/reference/examples/login Handles user registration. This endpoint is publicly accessible and involves pre-request handling for CORS. ```APIDOC ## POST /signup ### Description Handles user registration. This endpoint is publicly accessible and involves pre-request handling for CORS. ### Method POST ### Endpoint /signup ### Parameters #### Query Parameters None #### Request Body None ### Request Example None ### Response #### Success Response (200) - **message** (string) - A success message indicating user registration. #### Response Example { "message": "User registered successfully" } ``` -------------------------------- ### Controller Method Example for Business Logic Source: https://spine.na2ru2.me/ko/learn/core-concepts/pipeline An example of a controller method that focuses purely on business logic, such as fetching user data. It demonstrates input validation and interaction with a repository, adhering to the principle of separating concerns from HTTP handling. ```go func (c *UserController) GetUser(userId path.Int) (User, error) { if userId.Value <= 0 { return User{}, httperr.BadRequest("유효하지 않은 사용자 ID") } return c.repo.FindByID(userId.Value) } ``` -------------------------------- ### User Signup API Source: https://spine.na2ru2.me/ko/reference/examples/login Allows new users to register by providing their email, password, and name. This endpoint does not require authentication. ```APIDOC ## POST /signup ### Description Registers a new user with the provided email, password, and name. ### Method POST ### Endpoint /signup ### Parameters #### Request Body - **email** (string) - Required - User's email address. - **password** (string) - Required - User's password. - **name** (string) - Required - User's name. ### Request Example ```json { "email": "alice@example.com", "password": "1234", "name": "Alice" } ``` ### Response #### Success Response (200) - **id** (integer) - The unique identifier for the newly created user. - **email** (string) - The user's email address. - **name** (string) - The user's name. #### Response Example ```json { "id": 1, "email": "alice@example.com", "name": "Alice" } ``` ``` -------------------------------- ### Initialize and Run Spine Application Source: https://spine.na2ru2.me/ko/learn/getting-started/intro Demonstrates the basic setup of a Spine application, including dependency registration, interceptor configuration, route definition, and running the server. It showcases Spine's explicit pipeline for request execution. ```go func main() { app := spine.New() // 의존성 등록 — 순서 상관없이, 생성자만 등록하면 자동 해결 app.Constructor(NewUserRepository, NewUserService, NewUserController) // 인터셉터 — 실행 순서가 코드에 그대로 보임 app.Interceptor(&TxInterceptor{}, &LoggingInterceptor{}) // 라우트 — 어떤 메서드가 어떤 경로인지 명확 app.Route("GET", "/users", (*UserController).GetUser) app.Run(boot.Options{ Address: ":8080", EnableGracefulShutdown: true, ShutdownTimeout: 10 * time.Second, HTTP: &boot.HTTPOptions{}, }) } ``` -------------------------------- ### Spine Application Setup with Dependency Injection (Go) Source: https://spine.na2ru2.me/ko/learn/tutorial/5-database Demonstrates how to set up the Spine application and register dependencies using constructor injection. It shows how to provide the database connection, repositories, services, and controllers to the application. ```go // main.go func main() { app := spine.New() app.Constructor( NewDB, // *bun.DB repository.NewUserRepository, // bun.IDB → *UserRepository service.NewUserService, controller.NewUserController, ) routes.RegisterUserRoutes(app) app.Run(boot.Options{ Address: ":8080", EnableGracefulShutdown: true, ShutdownTimeout: 10 * time.Second, HTTP: &boot.HTTPOptions{}, }) } ``` -------------------------------- ### NewHandlerMeta 함수 생성 과정 상세 (Go) Source: https://spine.na2ru2.me/ko/learn/core-concepts/handler-meta NewHandlerMeta 함수의 생성 과정 상세 설명입니다. Step 1에서는 입력이 함수인지 검증하고, Step 2에서는 메서드 표현식인지 검증합니다. Step 3에서는 리시버가 포인터 타입인지 확인하고, Step 4에서는 runtime.FuncForPC를 사용하여 메서드 이름을 추출합니다. 마지막으로 Step 5에서는 리플렉션을 통해 메서드 정보를 획득합니다. ```go // Step 1: 함수 검증 t := reflect.TypeOf((*UserController).GetUser) t.Kind() // reflect.Func ✓ ``` ```go // Step 2: 메서드 표현식 검증 t.NumIn() // 2 (receiver + path.Int) t.In(0) // *UserController (receiver) t.In(1) // path.Int ``` ```go // Step 3: 메서드 이름 추출 fn.Name() // "github.com/NARUBROWN/spine-demo.(*UserController).GetUser" // ↑ methodName ``` ```go // Step 4: Method 획득 method, _ := reflect.TypeOf(&UserController{}).MethodByName("GetUser") // method.Name: "GetUser" // method.Type: func(*UserController, path.Int) (User, error) // method.Func: 호출 가능한 reflect.Value ``` -------------------------------- ### Bash cURL Command for Get User Info API Test (Unauthenticated) Source: https://spine.na2ru2.me/ko/reference/examples/login Uses cURL to send a GET request to the /me endpoint without an Authorization header. This tests the protected route's access control. ```bash curl http://localhost:8080/me ``` -------------------------------- ### Bash cURL Command for Get User Info API Test (Authenticated) Source: https://spine.na2ru2.me/ko/reference/examples/login Uses cURL to send a GET request to the /me endpoint with a valid JWT token in the Authorization header. This tests successful retrieval of user information after authentication. ```bash curl http://localhost:8080/me \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ``` -------------------------------- ### Get Current User API Source: https://spine.na2ru2.me/ko/reference/examples/login Retrieves information about the currently authenticated user. This endpoint requires a valid JWT token for authorization. ```APIDOC ## GET /me ### Description Retrieves information about the currently authenticated user. This endpoint requires a valid JWT token for authorization. It performs CORS pre-handling and route-level authentication to validate the token. ### Method GET ### Endpoint /me ### Parameters #### Path Parameters None #### Query Parameters None #### Request Body None ### Request Example None ### Response #### Success Response (200) - **id** (string) - The unique identifier of the user. - **username** (string) - The username of the user. - **email** (string) - The email address of the user. #### Response Example { "id": "user123", "username": "testuser", "email": "test@example.com" } #### Error Response (401) - **error** (string) - "Unauthorized" if the token is missing or invalid. #### Error Response Example { "error": "Unauthorized" } ``` -------------------------------- ### Get My Information API Source: https://spine.na2ru2.me/ko/reference/examples/login Retrieves the profile information of the currently authenticated user. This endpoint requires a valid JWT token in the Authorization header. ```APIDOC ## GET /me ### Description Retrieves the profile information of the currently authenticated user. Requires a JWT token in the Authorization header. ### Method GET ### Endpoint /me ### Parameters #### Query Parameters - **Authorization** (string) - Required - Bearer token for authentication (e.g., `Bearer YOUR_JWT_TOKEN`). ### Request Example (with token) ```bash curl http://localhost:8080/me \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." ``` ### Response #### Success Response (200) - **id** (integer) - The unique identifier for the user. - **email** (string) - The user's email address. - **name** (string) - The user's name. #### Response Example ```json { "id": 1, "email": "alice@example.com", "name": "Alice" } ``` #### Error Response (401 Unauthorized) - **message** (string) - Error message indicating missing or invalid token. #### Error Response Example ```json { "message": "토큰이 필요합니다." } ``` ``` -------------------------------- ### Spine Application Initialization and Route Registration (Go) Source: https://spine.na2ru2.me/ko/index Illustrates the main function in Go for initializing a Spine application. It shows how to register constructors for automatic dependency resolution and how to register user-related routes using a separate routes file. The application is then run with specified options. ```go // main.go func main() { app := spine.New() // ✅ 생성자만 등록하면 의존성 자동 해결 // ✅ 순서 상관없이 등록 가능 app.Constructor(NewUserRepository, NewUserService, NewUserController) routes.RegisterUserRoutes(app) app.Run(boot.Options{ Address: ":8080", EnableGracefulShutdown: true, ShutdownTimeout: 10 * time.Second, HTTP: &boot.HTTPOptions{}, }) } ``` -------------------------------- ### Controller Documentation (CRUD Operations) Source: https://spine.na2ru2.me/ko/learn/tutorial/8-swagger Documentation for controller methods, including examples for Get, Create, Update, and Delete user operations, with detailed parameter and response definitions. ```APIDOC ## Controller Documentation ### Basic Format This section shows the basic structure for documenting a controller method using Go comments and Swagger annotations. ```go // controller/user_controller.go package controller import ( "context" "myapp/dto" "myapp/service" "github.com/NARUBROWN/spine/pkg/httperr" "github.com/NARUBROWN/spine/pkg/query" ) type UserController struct { svc *service.UserService } func NewUserController(svc *service.UserService) *UserController { return &UserController{svc: svc} } // GetUser godoc // @Summary 유저 조회 // @Description ID로 유저 정보를 조회합니다 // @Tags users // @Param id query int true "User ID" // @Success 200 {object} dto.UserResponse // @Failure 404 {object} ErrorResponse // @Router /users [get] func (c *UserController) GetUser( ctx context.Context, q query.Values, ) (dto.UserResponse, error) { id := int(q.Int("id", 0)) user, err := c.svc.Get(ctx, id) if err != nil { return dto.UserResponse{}, httperr.NotFound("유저를 찾을 수 없습니다.") } return user, nil } ``` ### CRUD Full Example This example demonstrates how to document `GetUser`, `CreateUser`, `UpdateUser`, and `DeleteUser` methods using Swagger annotations. ```go // GetUser godoc // @Summary 유저 조회 // @Description ID로 유저 정보를 조회합니다 // @Tags users // @Param id query int true "User ID" // @Success 200 {object} dto.UserResponse // @Failure 404 {object} ErrorResponse // @Router /users [get] func (c *UserController) GetUser( ctx context.Context, q query.Values, ) (dto.UserResponse, error) { // ... } // CreateUser godoc // @Summary 유저 생성 // @Description 새로운 유저를 생성합니다 // @Tags users // @Accept json // @Produce json // @Param body body dto.CreateUserRequest true "유저 생성 요청" // @Success 200 {object} dto.UserResponse // @Failure 400 {object} ErrorResponse // @Router /users [post] func (c *UserController) CreateUser( ctx context.Context, req dto.CreateUserRequest, ) (dto.UserResponse, error) { // ... } // UpdateUser godoc // @Summary 유저 수정 // @Description 유저 정보를 수정합니다 // @Tags users // @Accept json // @Produce json // @Param id query int true "User ID" // @Param body body dto.UpdateUserRequest true "유저 수정 요청" // @Success 200 {object} dto.UserResponse // @Failure 404 {object} ErrorResponse // @Router /users [put] func (c *UserController) UpdateUser( ctx context.Context, q query.Values, req dto.UpdateUserRequest, ) (dto.UserResponse, error) { // ... } // DeleteUser godoc // @Summary 유저 삭제 // @Description 유저를 삭제합니다 // @Tags users // @Param id query int true "User ID" // @Success 200 // @Failure 404 {object} ErrorResponse // @Router /users [delete] func (c *UserController) DeleteUser( ctx context.Context, q query.Values, ) error { // ... } ``` ``` -------------------------------- ### Create Spine Project and Initialize Dependencies Source: https://spine.na2ru2.me/ko/learn/getting-started/first-app This snippet shows the commands to create a new project directory, initialize Go modules, and install the Spine framework. It's the first step in setting up a new Spine application. ```bash mkdir hello-spine && cd hello-spine go mod init hello-spine go get github.com/NARUBROWN/spine ``` -------------------------------- ### Go Controller for Authentication and User Management Source: https://spine.na2ru2.me/ko/reference/examples/login Implements signup, login, and user profile retrieval functionalities. Handles input validation, user data storage interaction, and JWT token generation for authentication. Requires user model, store, and interceptor packages. ```go package controller import ( "time" "login-example/interceptor" "login-example/model" "login-example/store" "github.com/NARUBROWN/spine/core" "github.com/NARUBROWN/spine/pkg/httperr" "github.com/golang-jwt/jwt/v5" ) type AuthController struct { userStore *store.UserStore } func NewAuthController(userStore *store.UserStore) *AuthController { return &AuthController{ userStore: userStore, } } // 회원가입 — 인증 불필요 func (c *AuthController) Signup(req model.SignupRequest) (model.User, error) { if req.Email == "" || req.Password == "" || req.Name == "" { return model.User{}, httperr.BadRequest("모든 필드를 입력해주세요.") } user, err := c.userStore.Create(req.Email, req.Password, req.Name) if err != nil { return model.User{}, httperr.BadRequest(err.Error()) } return *user, nil } // 로그인 — 인증 불필요 func (c *AuthController) Login(req model.LoginRequest) (model.LoginResponse, error) { if req.Email == "" || req.Password == "" { return model.LoginResponse{}, httperr.BadRequest("이메일과 비밀번호를 입력해주세요.") } user, err := c.userStore.FindByEmail(req.Email) if err != nil { return model.LoginResponse{}, httperr.Unauthorized("이메일 또는 비밀번호가 일치하지 않습니다.") } // 비밀번호 확인 (실제로는 bcrypt 등으로 해시 비교) if user.Password != req.Password { return model.LoginResponse{}, httperr.Unauthorized("이메일 또는 비밀번호가 일치하지 않습니다.") } // JWT 토큰 생성 token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "user_id": user.ID, "email": user.Email, "exp": time.Now().Add(24 * time.Hour).Unix(), }) tokenString, err := token.SignedString(interceptor.JWTSecret) if err != nil { return model.LoginResponse{}, httperr.BadRequest("토큰 생성에 실패했습니다.") } return model.LoginResponse{ Token: tokenString, User: *user, }, nil } // 내 정보 조회 — 인증 필요 func (c *AuthController) GetMe(ctx core.ExecutionContext) (model.User, error) { // AuthInterceptor가 저장한 userID 조회 userIDValue, ok := ctx.Get("userID") if !ok { return model.User{}, httperr.Unauthorized("인증 정보를 찾을 수 없습니다.") } userID := userIDValue.(int64) user, err := c.userStore.FindByID(userID) if err != nil { return model.User{}, httperr.NotFound("사용자를 찾을 수 없습니다.") } return *user, nil } ``` -------------------------------- ### Get Raw Event Payload (Go) Source: https://spine.na2ru2.me/ko/reference/api/execution-context Returns the raw byte slice of the event's payload. This is useful when you need to manually unmarshal or process the event data, for example, JSON payloads that need to be converted into Go structs. ```go Payload() []byte ``` ```go payload := ctx.Payload() // []byte (JSON) var event OrderCreated json.Unmarshal(payload, &event) ``` -------------------------------- ### Get Path Parameter (Go) Source: https://spine.na2ru2.me/ko/reference/api/execution-context Retrieves a specific path parameter value by its name. This is commonly used in HTTP routing to extract dynamic parts of a URL. For example, fetching a user ID from a path like /users/{id}. ```go Param(name string) string ``` ```go userId := ctx.Param("id") // "123" ``` -------------------------------- ### Declare Route in Application Source: https://spine.na2ru2.me/ko/learn/core-concepts/handler-meta Defines a route in the application by specifying the HTTP method, path, and the handler method. This example shows how to register a GET request for '/users/:id' to be handled by the GetUser method of UserController. This is the initial step in the routing configuration. ```go // cmd/demo/main.go app.Route("GET", "/users/:id", (*UserController).GetUser) ``` -------------------------------- ### Implement In-Memory User Store in Go Source: https://spine.na2ru2.me/ko/reference/examples/login Provides a basic in-memory implementation of a user store using a map and mutex for thread safety. It includes functions to create users, find users by email, and find users by ID. ```go // store/user_store.go package store import ( "errors" "login-example/model" "sync" ) type UserStore struct { mu sync.RWMutex users map[int64]*model.User nextID int64 } func NewUserStore() *UserStore { return &UserStore{ users: make(map[int64]*model.User), nextID: 1, } } func (s *UserStore) Create(email, password, name string) (*model.User, error) { s.mu.Lock() defer s.mu.Unlock() // 이메일 중복 체크 for _, u := range s.users { if u.Email == email { return nil, errors.New("email already exists") } } user := &model.User{ ID: s.nextID, Email: email, Password: password, Name: name, } s.users[s.nextID] = user s.nextID++ return user, nil } func (s *UserStore) FindByEmail(email string) (*model.User, error) { s.mu.RLock() defer s.mu.RUnlock() for _, u := range s.users { if u.Email == email { return u, nil } } return nil, errors.New("user not found") } func (s *UserStore) FindByID(id int64) (*model.User, error) { s.mu.RLock() defer s.mu.RUnlock() user, ok := s.users[id] if !ok { return nil, errors.New("user not found") } return user, nil } ``` -------------------------------- ### Repository Constructor with Dependency Injection (Go) Source: https://spine.na2ru2.me/ko/learn/tutorial/5-database Example of a repository constructor function in Go that accepts a bun.IDB interface, demonstrating dependency injection. The bun.IDB is automatically provided by the Spine framework. ```go // repository/user_repository.go // bun.IDB를 받으므로 *bun.DB가 자동 주입됨 func NewUserRepository(db bun.IDB) *UserRepository { return &UserRepository{db: db} } ``` -------------------------------- ### Conditionally Skip Transactions by Method Name in Go (Spine) Source: https://spine.na2ru2.me/ko/learn/tutorial/6-transaction This Go code implements a transaction interceptor that conditionally skips transaction creation for methods starting with 'Get' or 'List'. The `PreHandle` method checks the method name, and `AfterCompletion` handles commit or rollback based on the presence of a transaction in the context. ```go import ( "strings" "core" "bun" ) func (i *TxInterceptor) PreHandle(ctx core.ExecutionContext, meta core.HandlerMeta) error { methodName := meta.Method.Name // Get으로 시작하는 메서드는 트랜잭션 스킵 if strings.HasPrefix(methodName, "Get") || strings.HasPrefix(methodName, "List") { return nil } // 트랜잭션 시작 tx, err := i.db.BeginTx(ctx.Context(), nil) if err != nil { return err } ctx.Set("tx", tx) return nil } func (i *TxInterceptor) AfterCompletion(ctx core.ExecutionContext, meta core.HandlerMeta, err error) { v, ok := ctx.Get("tx") if !ok { return // 트랜잭션 없으면 스킵 } tx := v.(*bun.Tx) if err != nil { tx.Rollback() } else { tx.Commit() } } ``` -------------------------------- ### Example: Timing Interceptor Source: https://spine.na2ru2.me/ko/reference/api/interceptor An example interceptor that measures and logs the time taken for a request to be processed. ```APIDOC ## TimingInterceptor Example ### Description This `TimingInterceptor` measures the duration of a request from the `PreHandle` call to the `AfterCompletion` call and logs the elapsed time. ### Implementation ```go import ( "log" "time" "github.com/NARUBROWN/spine/core" ) type TimingInterceptor struct{} // PreHandle records the start time of the request. func (i *TimingInterceptor) PreHandle(ctx core.ExecutionContext, meta core.HandlerMeta) error { ctx.Set("timing.start", time.Now()) return nil } // PostHandle does nothing in this implementation. func (i *TimingInterceptor) PostHandle(ctx core.ExecutionContext, meta core.HandlerMeta) {} // AfterCompletion calculates and logs the request duration. func (i *TimingInterceptor) AfterCompletion(ctx core.ExecutionContext, meta core.HandlerMeta, err error) { if start, ok := ctx.Get("timing.start"); ok { elapsed := time.Since(start.(time.Time)) log.Printf("[TIMING] %s %s took %v", ctx.Method(), ctx.Path(), elapsed) } } ``` ``` -------------------------------- ### Spine DI: Organizing Constructors by Domain Source: https://spine.na2ru2.me/ko/learn/tutorial/3-dependency-injection Demonstrates how to group constructor registrations by domain (e.g., infrastructure, user domain, order domain) using multiple calls to `app.Constructor()`. This improves code organization and maintainability. ```go func main() { app := spine.New() // Infrastructure app.Constructor( NewDB, NewRedisClient, NewLogger, ) // User domain app.Constructor( repository.NewUserRepository, service.NewUserService, controller.NewUserController, ) // Order domain app.Constructor( repository.NewOrderRepository, service.NewOrderService, controller.NewOrderController, ) app.Run(boot.Options{ Address: ":8080", EnableGracefulShutdown: true, ShutdownTimeout: 10 * time.Second, HTTP: &boot.HTTPOptions{}, }) } ``` -------------------------------- ### Example: Drain and Dispatch Events Source: https://spine.na2ru2.me/ko/reference/api/execution-context An example showing how to drain published events from the EventBus after execution and dispatch them using a Dispatcher. ```go // PostExecutionHook에서 이벤트 drain func (h *EventDispatchHook) AfterExecution(ctx core.ExecutionContext, results []any, err error) { if err != nil { return } events := ctx.EventBus().Drain() if len(events) == 0 { return } h.Dispatcher.Dispatch(ctx.Context(), events) } ``` -------------------------------- ### ProductController에서 query.Values를 사용한 검색 API 예시 (Go) Source: https://spine.na2ru2.me/ko/learn/core-concepts/query-values Spine 프레임워크의 ProductController에서 query.Values를 사용하여 검색 API를 구현하는 예시입니다. 다양한 쿼리 파라미터(q, category, min_price, max_price, in_stock)를 처리하여 상품을 검색하고 결과를 반환합니다. ```go func (c *ProductController) Search(q query.Values) SearchResult { keyword := q.String("q") category := q.String("category") minPrice := q.Int("min_price", 0) maxPrice := q.Int("max_price", 999999) inStock := q.GetBoolByKey("in_stock", true) products := c.repo.Search(SearchCriteria{ Keyword: keyword, Category: category, MinPrice: minPrice, MaxPrice: maxPrice, InStock: inStock, }) return SearchResult{ Query: keyword, Count: len(products), Products: products, } } ``` -------------------------------- ### Get Request Method Source: https://spine.na2ru2.me/ko/reference/api/execution-context Returns the HTTP method (e.g., 'GET', 'POST') for HTTP requests or 'EVENT' for event messages. ```go Method() string ``` -------------------------------- ### Spine DI: Using Interfaces for Transaction Handling Source: https://spine.na2ru2.me/ko/learn/tutorial/3-dependency-injection Shows how to use interfaces, specifically `bun.IDB`, to handle dependencies that can be either a database connection (`*bun.DB`) or a transaction (`*bun.Tx`). This pattern is crucial for managing database transactions within the DI system. ```go // ✅ bun.IDB implements both *bun.DB and *bun.Tx type UserRepository struct { db bun.IDB } func NewUserRepository(db bun.IDB) *UserRepository { return &UserRepository{db: db} } // Example of injecting transaction in an interceptor: // interceptor/tx_interceptor.go func (i *TxInterceptor) PreHandle(ctx core.ExecutionContext, meta core.HandlerMeta) error { tx, err := i.db.BeginTx(ctx.Context(), nil) if err != nil { return err } ctx.Set("tx", tx) // Store transaction return nil } func (i *TxInterceptor) AfterCompletion(ctx core.ExecutionContext, meta core.HandlerMeta, err error) { tx, ok := ctx.Get("tx") if !ok { return } if err != nil { tx.(*bun.Tx).Rollback() } else { tx.(*bun.Tx).Commit() } } ``` -------------------------------- ### Project Structure Example Source: https://spine.na2ru2.me/ko/learn/tutorial/8-swagger An example project directory structure for a Spine-based application, including directories for controllers, DTOs, services, routes, and the generated docs. ```text myapp/ ├── main.go ├── docs/ │ ├── docs.go │ ├── swagger.json │ └── swagger.yaml ├── controller/ │ └── user_controller.go ├── dto/ │ ├── user_request.go │ └── user_response.go ├── service/ │ └── user_service.go └── routes/ └── routes.go ``` -------------------------------- ### Initialize Swagger Documentation (Bash) Source: https://spine.na2ru2.me/ko/learn/tutorial/8-swagger Commands to initialize Swagger documentation generation for a Go project. The `swag init` command scans Go source files for Swagger annotations and generates documentation files. ```bash # 프로젝트 루트에서 실행 swag init # 또는 main.go 경로 지정 swag init -g main.go ``` -------------------------------- ### 메서드 표현식을 사용한 핸들러 등록 (Go) Source: https://spine.na2ru2.me/ko/learn/core-concepts/handler-meta Spine 프레임워크는 메서드 표현식을 사용하여 핸들러를 등록합니다. 예시에서는 (*UserController).GetUser 메서드 표현식을 사용하여 라우트에 핸들러를 등록하는 방법을 보여줍니다. 메서드 표현식은 첫 번째 인자로 receiver가 변환된 일반 함수로 취급됩니다. ```go // cmd/demo/main.go app.Route( "GET", "/users/:id", (*UserController).GetUser, // 메서드 표현식 ) ``` ```go // 메서드 표현식의 실제 타입 func(*UserController, path.Int) (User, error) // ↑ receiver가 첫 번째 인자로 변환됨 ``` -------------------------------- ### Constructor Injection for Services and Controllers (Go) Source: https://spine.na2ru2.me/ko/index Demonstrates how to create constructor functions for UserService and UserController in Go, utilizing constructor injection for dependency management. This pattern is common in Go applications for setting up dependencies. ```go func NewUserService(repo *UserRepository) *UserService { return &UserService{repo: repo} } func NewUserController(svc *UserService) *UserController { return &UserController{svc: svc} } ``` -------------------------------- ### Example: Use Context for Cancellation Source: https://spine.na2ru2.me/ko/reference/api/execution-context An example demonstrating how to use the request context's Done channel to check for cancellation or timeouts within an interceptor's PreHandle method. ```go func (i *TimeoutInterceptor) PreHandle(ctx core.ExecutionContext, meta core.HandlerMeta) error { select { case <-ctx.Context().Done(): return ctx.Context().Err() default: return nil } } ``` -------------------------------- ### Sorting and Pagination with Bun ORM (Go) Source: https://spine.na2ru2.me/ko/learn/tutorial/5-database Shows how to implement sorting and pagination for database queries using the bun ORM. This example demonstrates ordering results by a timestamp in descending order and applying LIMIT and OFFSET clauses. ```go // ORDER BY created_at DESC LIMIT 10 OFFSET 20 err := r.db.NewSelect(). Model(&users). Order("created_at DESC"). Limit(10). Offset(20). Scan(ctx) ``` -------------------------------- ### Bash cURL Command for User Signup API Test Source: https://spine.na2ru2.me/ko/reference/examples/login Uses cURL to send a POST request to the /signup endpoint with JSON payload. This tests the user registration functionality. ```bash curl -X POST http://localhost:8080/signup \ -H "Content-Type: application/json" \ -d '{"email": "alice@example.com", "password": "1234", "name": "Alice"}' ``` -------------------------------- ### Consumer Event Name Resolver Example (Go) Source: https://spine.na2ru2.me/ko/reference/api/execution-context An example ArgumentResolver for consumer contexts that resolves the event name. It type-asserts the ExecutionContext to ConsumerRequestContext and returns the event name using the EventName() method. ```go func (r *EventNameResolver) Resolve(ctx core.ExecutionContext, meta ParameterMeta) (any, error) { // ConsumerRequestContext로 타입 단언 consumerCtx, ok := ctx.(core.ConsumerRequestContext) if !ok { return nil, fmt.Errorf("ConsumerRequestContext가 아닙니다") } return consumerCtx.EventName(), nil } ``` -------------------------------- ### HTTP Path Parameter Resolver Example (Go) Source: https://spine.na2ru2.me/ko/reference/api/execution-context An example ArgumentResolver that resolves path parameters for HTTP requests. It type-asserts the ExecutionContext to HttpRequestContext, retrieves the raw parameter value, and parses it into an integer. ```go func (r *PathIntResolver) Resolve(ctx core.ExecutionContext, parameterMeta ParameterMeta) (any, error) { // HttpRequestContext로 타입 단언 httpCtx, ok := ctx.(core.HttpRequestContext) if !ok { return nil, fmt.Errorf("HTTP 요청 컨텍스트가 아닙니다") } raw, ok := httpCtx.Params()[parameterMeta.PathKey] if !ok { return nil, fmt.Errorf("path param을 찾을 수 없습니다: %s", parameterMeta.PathKey) } value, err := strconv.ParseInt(raw, 10, 64) if err != nil { return nil, err } return path.Int{Value: value}, nil } ``` -------------------------------- ### API Test: Get User by ID Source: https://spine.na2ru2.me/ko/reference/examples/crud This bash command retrieves a specific user by their ID using a GET request to the /users/:id endpoint. The expected JSON response includes the details of the requested user. ```bash curl http://localhost:8080/users/1 ```