[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가지 부분으로 나뉘어 져 있다.
- 핸들러 목록생성하는 부분
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} 를 기술했음을 주위해야한다.
- 핸들러 함수를 기술하는 부분
핸들러 메소드 지정부분은 이전 코드와 동일하다. 핸들러에 대해서 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 에서 소스코드를 확인 가능