티스토리 뷰

반응형

지난 시간에 Go와 Database의 연동을 Gorm을 하기 위해 데이터 베이스를 생성해주고, 모델을 만들어 인터페이스로 묶어 연결시켜주었는데요, 아직 못 보신 분은 아래 글을 빠르게 읽고 넘어가도록 하겠습니다.

https://hero-space.tistory.com/138

 

Go와 Database 연동은 Gorm으로 시작!

백엔드 서버를 개발 할때, 데이터의 저장은 DB를 연동해서 하는 것이 일반적인데요. DB의 종류도 SQL로 할 꺼냐, NoSQL로 할꺼냐의 근본적인 결정도 있지만 SQL의 경우 데이터의 모델을 정의하고 이

hero-space.tistory.com

Echo : High Performance, Extensible, Minimalist Go Web Framework

https://echo.labstack.com/

 

Echo - High performance, minimalist Go web framework

Echo is a high performance, extensible, minimalist web framework for Go (Golang).

echo.labstack.com

Golang으로 여러가지 개발을 할 수 있지만 백엔드 친화적인 언어이기에 웹서버를 만들 때 유용하게 사용할 수 있는 프레임워크들이 많이 존재합니다. 프레임워크 자체는 개발 생산성을 향상시켜주기도 하지만 한편으로 제약이 있고 프레임워크 자체가 완벽하지 않을 수 있어서  어떤 수준의 성능과 기능을 가진 웹서버를 만드냐에 따라 선택해야 하는데요. 아무래도 많은 분들이 사용하고 있고, Echo와 비슷한 Gin 이라는 프레임워크에 비해 문서화가 잘 되어 있기에 꼭 필요 없더라도 사용해보시는 것을 추천합니다. echo 의 자세한 내용은 공식문서에서 확인하시고, 라우터를 만들어 보도록 하겠습니다.

Router 만들기

코드로 바로 보여 드리고 설명하도록 하겠습니다.

package router

import (
	"fmt"
	"log"
	"net/http"
	"strconv"
	"time"

	"gormTest/handler"
	md "gormTest/middleware"

	"github.com/labstack/echo/v4"
	"github.com/labstack/echo/v4/middleware"
)

func Router() (*echo.Echo, error) {
	e := echo.New()
	e.HTTPErrorHandler = handler.NewHttpErrorHandler(handler.NewErrorStatusCodeMaps()).ErrorHandler

	e.Debug = true

	e.Use(middleware.Logger())
	e.Use(middleware.Recover())
	e.Use(md.Cors)

	h, err := handler.NewHandler()
	if err != nil {
		return nil, err
	}

	e.GET("/", func(c echo.Context) error {
		return c.String(http.StatusOK, "Hello World")
	})

	userGroup := e.Group("api/v1/user")
	{
		userGroup.GET("", h.GetUser)
	}

	return e, nil
}

func StartServer() {
	defer func() {
		recover()
		log.Println("Recovery")
		retryDurationSecond, getEnvErr := strconv.Atoi("10")
		if getEnvErr != nil {
			log.Println(getEnvErr)
			panic(getEnvErr)
		}

		log.Println("Server Start Error -> Retrying after", fmt.Sprintf("%v", retryDurationSecond), "Seconds...")
		time.Sleep(time.Second * time.Duration(retryDurationSecond))

		go StartServer()
	}()

	server, err := Router()
	if err == nil {
		fmt.Println(" Server Start ")
		address := "0.0.0.0:8088"
		if err := server.Start(address); err != http.ErrServerClosed {
			panic(err)
		}
	} else {
		panic(err)
	}
}

아주 기본적인 코드로 Router의 핸들러를 생성하면서 옵션 세팅을 하고 있으며 URI 매핑을 하고 있는데요. "/" 로 접속했을때는 아무런 제약 없이 Hello World 를 스트링 값으로 리턴해주도록 되어 있고, api/v1/user 는 그룹으로 묶어서 GET 메소드의 경우 h.GerUser로 연결되어 있습니다. 그 밑에 스타트 서버의 경우는 서버의 리트라이와 재진입관점에서 main 함수에서 호출 될때 사용하는 것이니 입맛에 맞게 사용하시면 됩니다.

GetUser 가 있는 user.go 로 가보도록 하겠습니다.

package handler

import (
	"fmt"
	"net/http"

	"github.com/labstack/echo/v4"
)

func (h *Handler) GetUser(c echo.Context) error {

	// #1 Get user in DB
	email := fmt.Sprintf("%v", c.QueryParam("email"))
	if len(email) == 0 {
		return ErrEmailEmpty
	}

	user, err := h.db.GetUser(email)
	if err != nil {
		return ErrInternalServer
	}

	if len(user.Email) == 0 {
		return ErrEmailNotFoundInDB
	}

	retUser := JSONUser{
		Email:        user.Email,
		Name:         user.Name,
		Organization: user.Organization,
		Tag:          user.Tag,
	}

	return c.JSON(http.StatusOK, retUser)
}

저는 에러 핸들러는 별도 지정해 특정 에러마다 별도 enum 화해서 리턴되게 해놨는데요, 관련해서는 별도 글로 설명하도록 하겠습니다. 우선 GetUser에 email 이라는 쿼리파라메터와 함께 전달되어야하나, 만약 email이 없다면  에러가 리턴되어야겟죠? 물론 email과 같은 정보는 별도의 쿼리 파람메터가 아니라 token의 정보에서 파싱헤서 얻어질 수 있도록 할 수 도 있으나, 현재 테스트 코드는 authentication을 middleware에 넣지 않았기에 쿼리파라메터로 처리 했습니다. 

만약 정상적으로 email이 들어왔을 경우 orm으로 연결되어 있는 GetUser를 email로 find 해서 리턴 값이 있는지 없는지 따라 처리하도록 했습니다. 하번 결과를 볼까요?

위 요청은 localhost:8088/api/v1/user에 쿼리파라메터로 email에 test@gmail.com 인데요. 앞선 글에서 제가 생성했던 데이터는 captaintech.hero@gmail.com 이었죠? 따라서 리턴값이 없기에 저런 message를 200 ok에 리턴하도록 설정했습니다.

이번에는 정상적으로 정보가 올라오고 있죠? 쿼리파라메터에 DB에 존재하는 이메일 넣었기 때문입니다. 근데 조금 다란게 json의 리턴 값이 ORM을 위해 만든 값과 다른것 볼 수 있습니다. 

type User struct {
	ID           int    `json:"userId" gorm:"column:user_id;AUTO_INCREMENT;PRIMARY_KEY;not null"`
	Email        string `json:"email" gorm:"column:user_email;size:100;unique;not null"`
	Name         string `json:"name" gorm:"column:user_name;size:100"`
	Organization string `json:"organization" gorm:"column:organization;size:100"`
	Tag          string `json:"tag" gorm:"column:tag;size:100"`
	CreatedAt    int    `json:"createdAt" gorm:"column:created_at;type:int(13)"`
	UpdatedAt    int    `json:"updatedAt" gorm:"column:updated_at;type:int(13)"`
}

위 스트럭트가 ORM을 위해 생성된 것이라면 저는 핸들러의 타입을 별도로 생성했습니다.

type JSONUser struct {
	Email        string `json:"email"`
	Name         string `json:"name"`
	Organization string `json:"organization"`
	Tag          string `json:"tag"`
}

두개의 내용을 별도로 해도되고 같이 해도 되나, Client 관점에서 굳이 필요없는 정보는 뺐는데요. 이 부분은 백엔드 설계시 고려해서 개발하시면 됩니다. 저렇게 두개로 분리하면 장단점이 있겠죠?

반응형
댓글