본문 바로가기
golang

[golang] golang echo framework와 layered architecture를 활용한 백엔드 api 서버 구축하기

by devjh 2021. 7. 14.
반응형

이번에 golang으로 백엔드 api 서버를 구축하게 되어 관련내용 및 느낀점을 정리합니다.

 

1. 프레임워크 선택하기

여러 프레임워크를 살짝 맛본결과 echo가 좋다고 판단하여 echo를 선택하게 되었습니다.

 

2. why echo??

2021년 8월 기준 golang 웹 프레임워크 순위는 아래와 같습니다.(github star 수 기준)

(https://github.com/mingrammer/go-web-framework-stars)

Project Name Star Fork Open Issue Description Last Commit
gin 50305 5692 415 Gin is a HTTP web framework written in Go (Golang). It features a Martini-like API with much better performance -- up to 40 times faster. If you need smashing performance, get yourself some Gin. 2021-08-03 02:26:26
beego 26772 5265 30 beego is an open-source, high-performance web framework for the Go programming language. 2021-08-05 16:26:54
kit 20948 2161 51 A standard library for microservices. 2021-07-20 22:00:54
echo 20392 1810 56 High performance, minimalist Go web framework 2021-08-02 19:44:59
fasthttp 15709 1293 36 Fast HTTP package for Go. Tuned for high performance. Zero memory allocations in hot paths. Up to 10x faster than net/http 2021-08-04 18:15:05
mux 14866 1387 19 A powerful HTTP router and URL matcher for building Go web servers with 🦍 2020-09-12 19:20:56

gin이 압도적인 1위를 하고있고

 

beego, kit, echo가 뒤를 쫒고 있습니다.

 

중국에서 진을 많이 사용하고있어서 1등은 gin입니다.(golang을 가장 활발하게 사용하고 있는 나라는 중국)

 

한국에서는 echo를 많이 사용하고 있습니다.

 

국내에 reference가 그나마 많다는점과 공식문서에서 example을 제공하고있어(공식홈페이지의 get started가 천사 그 자체) 러닝커브 없이 접근하기 쉽다는 장점에 echo 프레임워크를 선택하게 됐습니다.

 

그리고 어떤 기능을 포함하냐도 중요한데 제가 원하는 기능들은 모두 포함하고 있습니다.

3. 공식홈페이지의 예시

공식홈페이지에는 하나의 go 파일에 다음과같이 crud를 구현해놓은예시가 있습니다만

package main

import (
	"net/http"
	"strconv"

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

type (
	user struct {
		ID   int    `json:"id"`
		Name string `json:"name"`
	}
)

var (
	users = map[int]*user{}
	seq   = 1
)

//----------
// Handlers
//----------

func createUser(c echo.Context) error {
	u := &user{
		ID: seq,
	}
	if err := c.Bind(u); err != nil {
		return err
	}
	users[u.ID] = u
	seq++
	return c.JSON(http.StatusCreated, u)
}

func getUser(c echo.Context) error {
	id, _ := strconv.Atoi(c.Param("id"))
	return c.JSON(http.StatusOK, users[id])
}

func updateUser(c echo.Context) error {
	u := new(user)
	if err := c.Bind(u); err != nil {
		return err
	}
	id, _ := strconv.Atoi(c.Param("id"))
	users[id].Name = u.Name
	return c.JSON(http.StatusOK, users[id])
}

func deleteUser(c echo.Context) error {
	id, _ := strconv.Atoi(c.Param("id"))
	delete(users, id)
	return c.NoContent(http.StatusNoContent)
}

func getAllUsers(c echo.Context) error {
	return c.JSON(http.StatusOK, users)
}

func main() {
	e := echo.New()

	// Middleware
	e.Use(middleware.Logger())
	e.Use(middleware.Recover())

	// Routes
	e.GET("/users", getAllUsers)
	e.POST("/users", createUser)
	e.GET("/users/:id", getUser)
	e.PUT("/users/:id", updateUser)
	e.DELETE("/users/:id", deleteUser)

	// Start server
	e.Logger.Fatal(e.Start(":1323"))
}

 

main.go 파일 하나에 crud를 구현해놓기만 해서 매우 부실합니다.

 

직접 프레임워크를 구성해봤습니다.

 

4. architecture

layered architecture에 근간을 두고있는 3-tier architecture를 사용하여 구현하였습니다.

 

handler, usecase, interactor, gateway, repository 등으로 컴포넌트를 쪼개도 되지만

저역시도 가장 친숙한 controller, service, serviceImpl, repository, repositoryImpl로 컴포넌트를 나눠 개발했습니다.
(현재는 handler, service, repository, gateway 등으로 사용하고 있습니다)

 

서비스단(유스케이스 인터렉터단)이 비대해지는걸 막기위해 domain model쪽에 비즈니스 로직을 구현하는게 좋다고는 하지만

가장 친숙한게 3tier이니 해당 방식으로 개발하였습니다.

 

echo의 기능을 많이 사용하려면 echo context를 아규먼트로 각 계층마다 전달해줘야하지만

예시에서는 db transaction을 echo보다는 gorm을 이용하여 처리하였으므로 context는 계층마다 전달하지 않았습니다.

 

controller, service, repository로 계층을 나눴지만, controller보다는 handler라는 이름을 사용할것을 추천합니다.
controller는 operator를 구축할때 종종 사용되는 네이밍으로 고랭 네이밍컨벤션에 위반될수 있으므로 애플리케이션 확장시 단점이 될수 있습니다.
주입오류를 파일에서 확인하고 싶으면 New의 리턴값으로 인터페이스를 리턴해도 됩니다.
golang에서 인터페이스를 리턴하면 마샬링등에서 에러가 나는 경우가 있지만 레이어를 구축할때는 고려하지 않아도 됩니다.

5. DB 접근방식

gorm을 사용하였습니다.

gorm은 기본적으로 join을 사용하지 않습니다.

외래키를 지정해서 간단하게 매핑해주면 쿼리를 두번 날려 조회해주는 방식입니다.

하나의 member는 여러개의 카드를 갖도록 구현해주면 아래처럼 쿼리가 두번 날라가서 entitfy object를 채워줍니다.

(

select * from member where id = 1

select * from card where member_id = 1

)

 

gorm은 항상 즉시로딩을 사용하며 세팅부터 매핑까지 매우 쉬우며 n+1 fetch join 같은걸 배우지 않아도 됩니다..

6. test

golang의 테스트 라이브러리

mockup의 경우 두 진영이 존재합니다.

 

- github.com/golang/mock/gomock

- github.com/stretchr/testify/mock

 

github start 수는 testify가 golang 기본 네임스페이스보다 많습니다.(13.8k, 5.9k)

 

mock 구현체는 golang/mock 진영은 mockgen을 사용하고

stretchr/testify 진영은 vektra/mockery를 사용하여 구현합니다.

 

양 진영에 장단점이 존재하지만,

편의성때문에 testify 를 더 많은 사람들이 사용하고 있습니다

 

ex) mock 구현체를 구현하는 명령어만 봐도 mockery가 편의성을 고려해준 모습이 보입니다.

 

go mock은 꼭 풀명세를 기록해줘야만 mock 구현체를 구현해주지만

$ mockgen -source="???" -destination="???" -package "???"

 

testify는 --all 만 써줘도 구현됩니다.

$ mockery --all

 

testify가 처음접하더라도 편리하게 사용할 수 있도록 default 옵션 구축을 상세하게 해준 모습이며 , 타 기능들도 전체적으로 사용하기에 더 편했습니다.

 

golang/mock은 전체적으로 안전성을 도모하는 느낌이지만 테스트쪽은 junit을 메인으로 사용하던지라 junit보다 너무 과하게 작성해야하는 gomock은 살짝 거부감이  생기는 느낌이었습니다.

 

stretchr/testify는 편의성은 좋지만 조금은 특이한(2% 부족한) 모습도 보입니다.

메서드의 행위를 모킹할때 메서드의 이름을 문자열로 넘긴다는 등 문자열로 명세를 하는 부분이 몇 존재합니다.

 

저는 testify, mockery 진영을 채택하였지만

 

두 진영에 대한 비교는 아래 블로그에 상세히 작성되어 있으니 아래에서 확인해보시고 선호하는 라이브러리를 사용하시면 될것 같습니다.

https://blog.codecentric.de/2019/07/gomock-vs-testify

 

GoMock vs. Testify: Mocking frameworks for Go

A comparison of popular Go mocking frameworks: Testify/mock and GoMock. We look at mock generation and usage, type safety, output for failing tests, and integration with Go tooling.

blog.codecentric.de

 

사용법

홈 디렉터리에서 아래 명령어를 입력해서 testify mock, mockery 의존성을 당겨온 후

$ go get github.com/stretchr/testify/mock
$ go get github.com/vektra/mockery/v2/.../

 

아래의 명령어를 사용하면 mock구현체가 생성됩니다.

$ mockery --all --keeptree

 

output directory를 선택하고 싶다면

$ mockery -all -output $PWD/mocks

 

원하는 인터페이스만 구현하고싶다면

$ mockery -name "MyInterface1|MyInterface2"

 

인터페이스마다 go generate의 명세를 작성하려면 인터페이스 위에다가 아래의 내용을 명시

//go:generate mockery --name MyInterface --case underscore

 

 

test코드 작성법

  • 통합테스트

golang testing과 echo(controller 사용을 위해)를 사용하여 테스트하며 영속성 계층이 의존하는 db만(직접 mock을 생성해 변경), 주입하여 테스트합니다.

 

모든계층을 한번에 테스트 할 수 있으며, db만 같다면 실제 운영환경과 같은 환경으로 테스트가 가능합니다. (운영환경과 db까지 같은환경을 원한다면 db도 변경없이 테스트)

 

  • 단위테스트

testify와 mockery를 사용했습니다.

각 계층간 interface역할을 하는 type ~~ interface의 mock을 mockery가 구현해주므로

원하는 계층이 원하는 로직을 실행하는지 손쉽게 테스트 할 수 있습니다.

 

6. 구현

https://github.com/jaeho310/golang-echo-sample

 

GitHub - jaeho310/golang-echo-sample: platform-echo-sample

platform-echo-sample. Contribute to jaeho310/golang-echo-sample development by creating an account on GitHub.

github.com

html, css, jquery를 사용하여 간단한 프론트엔드도 구현하였습니다.

 

패키지구조는 아래와 같습니다.

.
|-- README.md
|-- README2.md
|-- common
|   `-- logger.go
|-- controller
|   |-- api
|   |   |-- card.controller.go
|   |   |-- response_data.go
|   |   |-- user.controller.go
|   |   `-- user.controller_test.go
|   |-- dto
|   |   `-- dto.go
|   `-- web
|       `-- web.controller.go
|-- docs
|   |-- docs.go
|   |-- swagger.json
|   `-- swagger.yaml
|-- go.mod
|-- go.sum
|-- infrastructure
|   |-- database
|   |   `-- sqlstore.go
|   `-- server
|       `-- server.go
|-- main.go
|-- mocks
|   |-- repository
|   |   `-- UserRepository.go
|   `-- service
|       `-- UserService.go
|-- model
|   |-- card.go
|   `-- user.go
|-- report.json
|-- repository
|   |-- card.repository.go
|   |-- card.repositoryimpl.go
|   |-- card.repositoryimpl_test.go
|   |-- user.repository.go
|   |-- user.repositoryimpl.go
|   `-- user.repositoryimpl_test.go
|-- service
|   |-- card.service.go
|   |-- card.serviceimpl.go
|   |-- user.service.go
|   |-- user.serviceimpl.go
|   `-- user.serviceimpl_test.go
|-- swagger.png
|-- test.env
`-- view
    |-- static
    |   |-- css
    |   |   `-- common.css
    |   `-- js
    |       |-- card.js
    |       |-- delete.js
    |       |-- detail.js
    |       |-- list.js
    |       `-- user.js
    `-- templates
        |-- card.html
        |-- delete.html
        |-- detail.html
        |-- list.html
        `-- user.html

vue로 구현된 frontend는 여기에 있으니 vue 학습을 원하시는분은 참고하셔도 좋습니다.

https://github.com/jaeho310/vue-sample

 

GitHub - jaeho310/vue-sample

Contribute to jaeho310/vue-sample development by creating an account on GitHub.

github.com

 

반응형

댓글