[Go] Go Web Programming Basic 04 - Routing 모듈로 분리하기

Route using module

REST API 를 만들면 각 서비스 도메인에 따라 라우팅도 다양하게 일어나야한다.

이럴때 이전 예제와 같이 라우팅을 하나의 go 파일에 모두 작성하면 파일이 비대해지고, 관리도 쉽지 않게 될 것이 자명하다.

이럴때 Route를 수행하는 모듈을 만들고 코드를 분리하면 관리도 편하고, 필요에 따라 코드 작성도 쉽게 수행할 수 있는 이점이 있다.

Route 모듈 작성하기.

지금은 패키지를 사용하지 않고 동일한 패키지 내에서 라우팅 모듈을 작성할 것이다.

route_module.go 파일을 하나 만들고 다음과 같이 모듈을 작성하자.

package main

import (
	"net/http"

	"github.com/gorilla/mux"
)

// 핸들러 구조체를 작성한다.
// 핸들러의 기본 내용은 경로, 핸들러함수, 메소드 이므로 3가지를 작성했다.
type handler struct {
	path    string
	fun     http.HandlerFunc
	methods []string
}

// 핸들러 구조체 목록을 받아서 핸들러를 등록한다.
func makeHandlers(hs []handler, r *mux.Router, prefix string) {
	apiRouter := r.PathPrefix(prefix).Subrouter()

	for _, h := range hs {
		if len(h.methods) == 0 {
			apiRouter.PathPrefix(h.path).HandlerFunc(h.fun)
		} else {
			apiRouter.PathPrefix(h.path).HandlerFunc(h.fun).Methods(h.methods...)
		}
	}
}

위 내용을 보면 우선 구조체를 생성했다.

이전 코드에서 알 수 있듯이 라우팅을 위한 구성 요소는 path, handler함수, 메소드 가 필요하므로 위와 같이 구조체를 정하였다.

이후 makeHandlers 는 핸들러 목록을 받고, 라우터, 프리픽스를 입력 받아서 라우팅을 생성하고 있다.

내용을 보면 알듯이 핸들러 슬라이스를 순회하면서 라우팅에 등록해주는 작업을 수행한다.

메소드가 존재하지 않는경우 단지 HandlerFunc 만 등록하며, 메소드가 존재하는 경우 메소드를 위와 같이 등록해 준다.

Router 등록을 위한 api 모듈 작성하기

이제 필요한 작업은 api 를 모듈로 분리하는 것이다.

이전 예제에서 확인한 것과 같이 우리는 users와 subjects 라는 엔드포인트를 이용하여 REST API 를 작성했었다.

  • /api/users: api_users.go 파일을 만들고, users 에 대한 서비스 엔드포인트를 작성한다.
  • /api/subjects: api_subjects.go 파일을 만들고, subjects 에 대한 서비스 엔드포인트를 작성한다.

이제 main.go 를 분리하여 api_users.go, api_subjects.go 파일을 작성해보자.

api_users.go 파일 작성하기.

  • /api/users: POST 로 사용자 등록을 수행한다.
  • /api/users: GET 으로 사용자 전체 목록을 조회한다.
  • /api/users/{id}: GET으로 특정 사용자 아이디 내역을 반환한다.
  • /api/users: PUT 으로 사용자 정보를 수정한다.
  • /api/users/{id}: DELETE 으로 사용자 id를 이용하여 사용자를 삭제한다.

위와 같이 사용자에 대한 api 엔드포인트를 정의했다. 이제 코드를 생성해 보자.

api_users.go

package main

import (
	"fmt"
	"net/http"
)

func usersHandlers() []handler {
	return []handler{
		{path: "/users/{id}", fun: UserByID, methods: []string{"GET"}},
		{path: "/users/{id}", fun: DeleteUserByID, methods: []string{"DELETE"}},
		{path: "/users", fun: CreateUser, methods: []string{"POST"}},
		{path: "/users", fun: AllUsers, methods: []string{"GET"}},
		{path: "/users", fun: UpdateUser, methods: []string{"PUT"}},
	}
}

func CreateUser(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "CreateUser")
}

func AllUsers(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "AllUsers")
}

func UserByID(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "UserByID")
}

func UpdateUser(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "UpdateUser")
}

func DeleteUserByID(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "DeleteUserByID")
}

위 코드를 보면 2가지 부분으로 나뉘어 져 있다.

  1. 핸들러 목록생성하는 부분
func usersHandlers() []handler {
	return []handler{
		{path: "/users/{id}", fun: UserByID, methods: []string{"GET"}},
		{path: "/users/{id}", fun: DeleteUserByID, methods: []string{"DELETE"}},
		{path: "/users", fun: CreateUser, methods: []string{"POST"}},
		{path: "/users", fun: AllUsers, methods: []string{"GET"}},
		{path: "/users", fun: UpdateUser, methods: []string{"PUT"}},
	}
}

우리가 정의한 핸들러를 기술하고 있다.

주의: 핸들러는 위에서 아래로 먼저 매칭되는 핸들러로 요청을 보내기 때문에 /users 보다 먼저 /users/{id} 를 기술했음을 주위해야한다.

  1. 핸들러 함수를 기술하는 부분

핸들러 메소드 지정부분은 이전 코드와 동일하다. 핸들러에 대해서 fun 에 매핑되는 함수를 만들며 만들어 주면 된다.

api_subjects.go 파일 작성하기.

  • /api/subjects: POST 로 subjects 등록을 수행한다.
  • /api/subjects: GET 으로 subjects 전체 목록을 조회한다.
  • /api/subjects/{id}: GET으로 특정 subjects 아이디 내역을 반환한다.
  • /api/subjects: PUT 으로 subjects 정보를 수정한다.
  • /api/subjects/{id}: DELETE 으로 subjects id를 이용하여 subjects를 삭제한다.

위와 같이 subjects 에 대한 api 엔드포인트를 정의했다. 이제 코드를 생성해 보자.

코드의 형식은 api_users.go 와 동일하다.

api_subjects.go

package main

import (
	"fmt"
	"net/http"
)

func subjectsHandlers() []handler {
	return []handler{
		{path: "/subjects/{id}", fun: SubjectByID, methods: []string{"GET"}},
		{path: "/subjects/{id}", fun: DeleteSubjectByID, methods: []string{"DELETE"}},
		{path: "/subjects", fun: CreateSubjects, methods: []string{"POST"}},
		{path: "/subjects", fun: AllSubjects, methods: []string{"GET"}},
		{path: "/subjects", fun: UpdateSubjects, methods: []string{"PUT"}},
	}
}

func CreateSubjects(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "CreateSubjects")
}

func AllSubjects(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "AllSubjects")
}

func SubjectByID(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "SubjectByID")
}

func UpdateSubjects(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "UpdateSubjects")
}

func DeleteSubjectByID(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "DeleteSubjectByID")
}

main.go 수정하기.

이제는 핸들러 등록 모듈과, 각 api 핸들러 모듈을 만들었으므로 하나로 조합해 보자.

main.go 메소드의 내용을 모두 지우고 다음과 같이 변경하자.

package main

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

	"github.com/gorilla/mux"
)

func main() {

	var handlers []handler
	handlers = append(handlers, usersHandlers()...)
	handlers = append(handlers, subjectsHandlers()...)

	router := mux.NewRouter()
	makeHandlers(handlers, router, "/api")

	fmt.Println("Server start on port: ", 8080)
	log.Fatal(http.ListenAndServe(":8080", router))
}

  • handlers = append(handlers, usersHandlers()…): 슬라이스에 새로운 핸들러 슬라이스를 append 하고 있다. 이렇게 우리가 만든 핸들러를 반복해서 등록해주면 된다.
  • makeHandlers(handlers, router, “/api”): prefix는 /api 이고 핸들러 목록과 router 전달해서 subrouter 를 등록했다.

실해하기.

지금까지 작성이 완료 되었으면 이제 실행해보자.

테스트를 go run … 을 수행하기 우해서는 모듈 명을 모두 기술해 주어야한다.

 go run main.go route_module.go api_subjects.go api_users.go
Server start on port:  8080

테스트 하기.

 curl http://localhost:8080/api/users/1
UserByID                                                                                   
 curl http://localhost:8080/api/users/ -X POST
CreateUser                                                                              
 curl http://localhost:8080/api/subjects/1
SubjectByID                                                                          
 curl http://localhost:8080/api/subjects/ -X POST
CreateSubjects

기대했던 결과를 확인 할 수 있다.

Wrap Up

모듈을 분리하여 REST Api 의 용도에 따라 분리된 파일로 작성했다.

이렇게 함으로써 소스도 깔끔하게 관리 되었고, 모듈도 동일한 일들을 수행하는 기능으로 묶어져서 전번적으로 Coheision 향상시켜 보았다.

관련 Git 에서 소스코드를 확인 가능