[루비에서 Go로 넘어가기] Revel을 이용해 MVC 웹 구성하기

Ruby에서 golang으로 주력 언어를 바꿔가는 중입니다. 이 과정에서 가장 문제가 되는 부분이 웹앱이였습니다. 이미 Rails에 익숙해진터라 대안이 될만한 여러 프레임워크를 찾아봤고, 2가지 생각이 나왔습니다.

  • revel을 통한 개발(MVC)
  • 기본으로 제공되는 net/http와 VueJS 조합

go에 MVC가 어울리는지는 모르겠으나, 우선 Rails의 익숙함을 채워줄 것이 필요했기 때문에 Revel부터 진행했습니다. 그리고 단일 페이지에 Vue로 프론트 구성하고, 백엔드 API로 동작 구현하는 2번 방식도 자주 사용해볼까 합니다.

아무튼 오늘 글은 1번에 대한 내용이고, Revel 사용하는 방법 정도만 정리해둡니다.

Install revel

go get으로 revel을 설치해줍시다.

go get -u github.com/revel/cmd/revel

쓰기 편하게 alias 등록해줍시다.

vim ~.zshrc
alias revel='/Users/hahwul/go/bin/revel' # 추가
source ~/.zshrc

자 초기 세팅은 끝났습니다.

revel 
Usage:
  revel [OPTIONS] <command>

Application Options:
  -v, --debug              If set the logger is set to verbose
      --historic-run-mode  If set the runmode is passed a string not json
  -X, --build-flags=       These flags will be used when building the application. May be specified multiple times,
                           only applicable for Build, Run, Package, Test commands

Available commands:
  build
  clean
  new
  package
  run
  test
  version

Create New WebApp

gopath로 이동해서 프로젝트를 생성해줍니다.

cd $GOPAHT(~/go)
revel new -a teszz
Revel executing: create a skeleton Revel application
Your application has been created in:
   /Users/hahwul/go/src/teszz

기본적으로 구조는 잡아주고, revel run 명령으로 실행할 수 있습니다.

revel run -a teszz
Revel executing: run a Revel application
WARN  01:34:22    run.go:150: No http.addr specified in the app.conf listening on localhost interface only. This will not allow external access to your application
INFO  01:34:24    app     run.go:26: Running revel server
INFO  01:34:24    app revel_hooks.go:32: Go to /@tests to run the tests.
Revel engine is listening on.. localhost:49464
Revel proxy is listening, point your browser to : 9000

서버의 기본 포트는 9000번입니다. 웹 브라우저로 접속해보면…

Revel App의 구조 파악하기

처음에 이야기드렸던대로 MVC 모델을 따르며, 전반적으로 Rails랑 비슷한 느낌이 많이듭니다.

cd ~/go/src/testzz
tree
.
├── README.md
├── app
│   ├── controllers
│   │   └── app.go
│   ├── init.go
│   ├── routes
│   │   └── routes.go
│   ├── tmp
│   │   ├── main.go
│   │   └── run
│   │       └── run.go
│   └── views
│       ├── App
│       │   └── Index.html
│       ├── debug.html
│       ├── errors
│       │   ├── 404.html
│       │   └── 500.html
│       ├── flash.html
│       ├── footer.html
│       └── header.html
├── conf
│   ├── app.conf
│   └── routes
├── messages
│   └── sample.en
├── public
│   ├── css
│   │   └── bootstrap-3.3.6.min.css
│   ├── fonts
│   │   ├── glyphicons-halflings-regular.ttf
│   │   ├── glyphicons-halflings-regular.woff
│   │   └── glyphicons-halflings-regular.woff2
│   ├── img
│   │   └── favicon.png
│   └── js
│       ├── bootstrap-3.3.6.min.js
│       └── jquery-2.2.4.min.js
└── tests
    └── apptest.go
  • app 디렉토리: 어플리케이션(view, controller)
  • conf: 설정정보,라우팅
  • public: 라우팅 없이 불러올 수 있는 퍼블릭 디렉토리
  • tests: 기본 유닛테스트

Routing

라우팅은 config/routes 를 통해 설정할 수 있습니다. 아래와 같은 형태를 띄며, URL과 페이지, 컨트롤러를 매핑할 수 있습니다.

module:testrunner
# module:jobs

GET     /                                       App.Index

# Ignore favicon requests
GET     /favicon.ico                            404

# Map static resources from the /app/public folder to the /public path
GET     /public/*filepath                       Static.Serve("public")

View and Template

위에 라우팅에서 / 를 가리키는 App.index 의 실제 뷰인 아래 코드를 봐봅시다.

cat app/views/App/Index.html
{{set . "title" "Home"}}
{{template "header.html" .}}

<header class="jumbotron" style="background-color:#A9F16C">
  <div class="container">
    <div class="row">
      <h1>It works!</h1>
      <p></p>
    </div>
  </div>
</header>

<div class="container">
  <div class="row">
    <div class="span6">
      {{template "flash.html" .}}
    </div>
  </div>
</div>

{{template "footer.html" .}}

일반 html 파일이지만, 기본적으로 template를 사용합니다. 구문은 {{template $}} 형태이며, 이를 이용해 어플리케이션 데이터를 직접 사용하거나, 외부 파일을 include 시킬 수 있습니다.

Controller

컨트롤러도 Rails와 거의 유사합니다.

cat app/controllers/app.go
//..snip..
type App struct {
 *revel.Controller
}

func (c App) Index() revel.Result {
 return c.Render()
}

헤더랑 코드 설정하는 거 몇가지 남겨둡니다.

type App struct {
 *revel.Controller
}

// ContentType이나 Statsu 코드, 헤더 등의 추가
func (c App) Index() revel.Result {
  c.Response.Status = http.StatusTeapot
  // or c.Response.Status = 301, 302, 404, etc..
 c.Response.ContentType = "application/json"
 return c.Render()
}

// 외부 Redirect도 Rails와 유사함..
func (c App) redirect_302() revel.Result {
  data := "callback?"
 return c.Redirect("/otherpath/%d", data)
}

Conclusion

사실 글 작성한지는 좀 됬습니다. 저장만 해뒀다가 이제야 글을 올리게 되네요. 루비에서 고로 바꾸면서 PoC 코드의 작성 범위가 좀 넓어진 느낌이긴합니다. 툴도 하나씩 포팅하고, 신규 툴은 고로 만들고 있는데, 루비랑은 다르게 또 다른 재미가 있네요.