Flying Mate

jsp to rails

2011/04/15

카울리라는 모바일 광고 플랫폼을 만들고 있습니다. 서비스 개발 부문을 맡으면서 기존의 관리자 CMS와 사용자 CMS를 개편하게 되었는데 기존에는 두 CMS가 모두 none-framework JSP로 구축되어 있었습니다. 생산성과 안전성을 고려해 Framework의 도입은 꼭 필요한 상황이었습니다. Spring도 고려하였고, PHP Codeigniter도 논의되었으나 결국 Rails3로 재구축하기로 하고 개발이 진행되었습니다.

서비스 개발팀이 담당하는 부분이 사용자 CMS, 관리자 CMS, 광고 서버, OS별 SDK 등 상당히 방대한데 개발 리소스는 제한되어 있으면서 사용자/관리자 두 CMS는 지속적이고 획기적으로 개선이 되어야 하는 상황이었습니다. 일일 페이지뷰 3000만 요청을 처리하는 광고 서버와 비교해, 상대적으로 Performance 보다는 Productivity가 중요한 웹 인터페이스는, 개발 생산성이 뛰어난 Ruby on Rails로 진행하기로 결정되었습니다.

결론부터 말하자면, 코드량과 개발 생산성에 있어서 획기적인 향상이 있었습니다.

  • 코드량이 1/6 수준으로 줄었습니다 (직접 작성한 코드를 위주로 wc를 이용해 카운팅)

  • 수 시간, 수 일 단위였던 과업 소요시간이 수 분에서 수 시간 단위로 단축되었습니다

  • 배포에 걸리는 시간은 수 초로 단축되고 배포 버젼을 관리해 비상시 이전 배포버젼 롤백이 간편합니다.

  • 뷰 컨텐츠를 Escaping하여 XSS공격에서 안전하고 POST 요청시 token을 사용해 비정상적인 요청을 차단합니다.

  • 중복되는 코드가 제거되고 MVC로 분리 및 모듈화되어 로직의 수정이나 기능의 추가가 빨라졌습니다.

  • 뷰에서 스타일과 컨텐츠가 분리되어 컨텐츠의 추가나 스타일의 변경에 빠르게 대응할 수 있습니다.

  • 요청시 개발단에서 쿼리 수작업으로 처리하던 일을 운영단에서 직접 처리할 수 있도록 관리자 기능을 확충했습니다.

  • 해외 진출에 빠르게 대응할 수 있도록 tzinfo, i18n과 talk등을 탑재하였습니다.

  • 그리고 개발이 재밌습니다. (Happy Coding)

주요 키워드를 중심으로 생산성이 향상되고 코드량이 줄어들게 할 수 있었던 노하우를 공유할까 합니다.

MVC

주요 웹 프레임워크, 그리고 요즘에는 게임 프레임워크나 클라이언트 프로그래밍에서도 활용하고 있는 MVC(모델, 뷰, 컨트롤러) 패턴을 이용해 JSP에 섞여 있던 쿼리, 예외처리, 뷰, 라이브러리 로드, 자바스크립트 등을 역할에 따라 적절히 분산시켰습니다. Rails에서는 기본 골격이 MVC이고, MVC로 개발하지 않겠다면 Rails를 도입하는 것 자체가 무의미해집니다. 쿼리문과 비즈니스 로직은 모델에 모아지고 요청에 대한 디테일한 처리는 컨트롤러, 아웃풋은 뷰가 담당하게 됩니다.

SCOPE

Rails는 Active Record패턴을 이용해 데이터베이스 테이블을 모델로 매핑합니다. Rails의 모델에는 Association, Validation, Scope, Method 등의 주요 부분이 있습니다. Scope는 자주 사용되는 SQL 조각(select, order by, join, where 등)을 메서드처럼 정의해서 호출할 수 있는데, 호출한 결과물이 resultset이 아니라 조합된 쿼리문 상태(실제로는 relation 객체)이기 때문에 최종적으로 호출되는 쿼리문 수를 줄이면서도 효율적으로 컨디션을 관리 및 편집할 수 있어서 유용합니다. 예를들면 모델에 다음과 같이 정의하고

scope :name, select("name")
scope :idle, where(:status => "idle")
scope :male, where(:gender => "male")
scope :lastest, order("last_logged_in desc")

컨트롤러에서 아래와 같이 메서드 형태의 체인을 호출하면

User.lastest.idle.male.name

최근에 로그인 한 순서로 자리를 비운 남성 이용자들의 이름을 반환하는 쿼리를 생성해줍니다. Method나 Association과는 달리 Scope는 각 체인마다 쿼리가 날아가는 것이 아니라 쿼리문만 조합하고 최종적으로 resultset이 필요한 순간에 쿼리를 한 번만 날리기 때문에 메서드가 가진 모듈화로서의 특징과 쿼리를 직접 생성했을 때의 효율성을 모두 취할 수 있습니다.

Validation

Validation 함수들은 데이터 입력시, 수정시 데이터 유효성을 확보해 줍니다. 또한 각 Validation 함수마다 예외처리 동작과 메시지를 지정해 놓으면 컨트롤러에서 동일한 오류에 대한 예외처리를 이곳 저곳에서 해줄 필요 없이 모델 단계에서 예외처리를 하고 적절한 피드백을 구성해서 사용자에게 송출하게 됩니다. 데이터베이스 레벨의 Validation은 부적절한 데이터값이 입력되지 않는다는 것만을 보장하지 사용자에게 어떤 메시지를 표시하고 어떤 예외처리를 할지는 애플리케이션 레벨에서 처리해줘야 하는데, Rails의 Active Record에서 지원하는 Validation 메서드는 이를 편리하게 해줍니다.

개발도 사람이 하는 것이기 때문에 코드 곳곳에 개발자의 성격과 기분이 묻어나기 마련인데요, 유효성 체크가 번거롭지 않고 편리하다는 사실 하나만으로도 개발자가 더 적극적으로 유효성 체크를 하게 됩니다. 이는 유효성 체크 뿐만 아니라 중복이 제거되고 코드가 간결해지면서 얻게 되는 긍정적인 사이드 이팩트인데, ruby라는 언어가 가진 간결함의 특징과 Rails가 지원하는 모듈화로 인해 기능의 개선과 유지보수가 더 쉽고 간단해지기 때문에 개발에 대해 덜 방어적이 되고 리팩토링과 아키텍쳐 개선, 또는 비즈니스적인 개선에도 신경을 쏟을 여유가 생깁니다. JSP에서는 여러 분기의 if, else로 예외처리 되어 있던 Validation 처리들이 모델에서는 주요 필드에 Whitelist pattern 및 메시지로 정의되면 됩니다.

DB Connection

데이터베이스가 Master / Slave로 Replication이 되어 있거나 Sharding이 필요한 상황이라면 Rails 모델에 establish_connection 설정만으로 해당 모델이 연결해야 하는 DB Connection을 지정할 수 있습니다. Rails Application을 통해 Update / Insert가 일어나는 모델에는 Master를 지정해주고 Select만 발생하는 모델에는 Slave를 지정해주면 됩니다. 모델별이 아니라 컨트롤러 액션별로 Connection이 달라져야 한다면 OctopusDB-Charmer를 이용하는 것이 편리합니다.

Filter

모델에 Validation이 있다면 컨트롤러에는 Filter가 있습니다. Validation이 데이터 유효성 검증이라면 Filter는 상태 및 동작 수행 검증이라고 할 수 있습니다. 가장 빈번하게는 사용자 인증 및 권한 상태 검증으로 이용되고 그밖에 요청이 일어날 때마다 사용자 언어 설정이나 PV 카운팅이 필요할 때 이용할 수 있죠. Filter 개념을 이용하지 않으면 비인증에 대한 처리나 요청이 있을 때마다 빈번하게 처리되는 코드 조각을 각 뷰에 모두 담아야 합니다.

위에서도 언급했지만 동일한 코드가 분산되는 것은 단지 코드를 많이 작성하는 것 이상의 손실이 있습니다. 유지보수가 어렵고 수정이 발생할 시에 오류가 발생할 확률이 더 크며 협업하기 어려워집니다. 그리고 제가 가장 중요하게 생각하는 Happy Coding을 방해하게 됩니다. Filter개념을 활용하지 않더라도 코드조각을 여러 곳에 include하는 방식이면 중복이 어느 정도는 제거되지만 해당 코드를 include하는 코드 자체도 상당한 중복이 되기 때문에 뷰 레벨이 아닌 컨트롤러 레벨에서 처리되는 Filter가 그보다 효율적인 방식이라고 할 수 있습니다.

Routing

none-framework JSP나 PHP는 파일 디렉토리 구조와 파일명이 URL이 되지만 MVC Framework에서는 파일명과는 관계 없이 URL 패턴에 대한 정의를 별도로 해주게 됩니다. Rails에서는 routes.rb파일이 Routing를 담당하며 기존에는 controller/action/id 와 같은 패턴의 마스크를 주로 사용했지만 Rails 2.0 이후부터는 위의 패턴을 디폴트 주석처리하고 RESTful 패턴을 활용한 resources routing을 권장하고 있습니다.

이는 역시 안전성 때문인데요 controller/action 패턴은 컨트롤러 내의 모든 public 메서드를 노출시키고 노출되지 말아야할 메서드를 private으로 지정해놓지 않았다면 해당 경로가 사용자에 의해서든 검색 크롤러에 의해서든 호출되었을 때 문제가 생깁니다. 때문에 private을 제외한 나머지가 요청 경로라는 블랙리스트 개념의 라우팅 대신 사전에 정의된 CRUD액션을 디폴트로 제시하고 이외의 요청경로는 별도로 라우팅에 지정해줘야한다는 화이트리스트 개념의 라우팅을 권장하고 있습니다.

아래 한 줄의 코드는 user 데이터와 관련한 CRUD 액션(index, show, new, create, edit, update, destroy)을 처리하는 7개의 경로(/users에 대한 post와 get, /users/id에 대한 post, get, update, destroy, /users/id/edit에 대한 get)를 자동으로 생성합니다.

resources :users

HAML

HAML은 논란이 많은 meta language입니다. indentation을 이용해 루비 코드가 삽입된 html을 간단하게 생성할 수 있고 뷰 코드량이 상당히 간결해지는 반면에 개발직군이 아니라면 코드에 손을 댈 수 없어 국내처럼 개발 출신이 아닌 퍼블리셔가 따로 있는 경우는 퍼블리셔가 직접 수정하기 어렵다는 단점이 있습니다. 특히 table 테그로 레이아웃을 잡고 CSS를 사용하지 않는 퍼블리셔로부터 받은 코드라면 HAML 적용이 불가능합니다. 국내 루비 커뮤니티에서도 HAML에 대한 찬반 논란이 있었습니다.

하지만 웹표준 퍼블리셔가 작성하고 CSS가 분리된 코드라면 간단한 커멘드로 HTML을 HAML로 변환할 수 있고 HTML에 더 가까운 ERB 템플릿을 이용하는 것보다 코드량이 획기적으로 감소하고 유지보수가 더 빠릅니다. 한 예로 500라인, 30000개 문자열로 작성된 JSP파일은 30 라인, 1000개 문자열의 HAML로 떨어졌습니다. 비즈니스 로직은 모델에, 요청에 대한 처리는 컨트롤러에, 스타일은 CSS로 분리되고 HAML 자체도 코드량을 감소시키기 때문인데요, 뷰가 수정될 때 500라인의 파일을 고치는 것보단 30라인의 파일을 고치는 게 더 쉽겠죠?

삽입되는 Content가 결정되고 성격에 맞는 레이블링(id와 class 설정)이 되었다면 CSS 수정만으로도 디자인 개선이 가능하기 때문에 롤을 어떻게 정하느냐에 따라 HAML의 협업 이슈는 크지 않다고 보고 있습니다. 서버 개발자가 기초적인 퍼블리싱이 가능하다면 위와 같은 meta language 적용과 동시에 디자인이 빠진 프로토타입 단계에서 직접 id/class 레이블링과 CSS 골격을 잡을 수 있고 실서비스 단계에서 Background-image 삽입 및 CSS 스타일 수정만으로도 디자인/스타일링이 완성될 수 있기 때문에 뷰 파일은 실제적인 컨텐츠로만 구성되고 디자인/스타일은 뷰 밖으로 빠지는 형태가 가능해집니다.

LESS

HAML이 HTML meta language라면 LESS는 CSS meta language 입니다. CSS를 바로 작성하지 않고 LESS에 작성한 후 CSS로 컨버팅하는 방식으로 사용합니다. 기본적으로는 CSS와 동일한데 몇 가지 특징이 추가되어, 계속 강조하고 있는 중복제거를 도와주는 stylesheet language 입니다. 가장 큰 특징으로는 변수와 메크로 함수(Mixin)를 사용할 수 있어 빈번하게 사용되는 색상코드 등을 변수로 지정하거나 자주 사용되는 스타일 코드를 함수 형태로 호출할 수 있습니다. 그리고 제가 가장 좋아하는 Nesting을 이용하면 parent selector를 매번 적어주지 않아도 되고 네임스페이스 형태로 스타일 코드를 관리할 수 있습니다.

LESS파일을 CSS로 변환하기 위해서는 LESS gem을 이용해 LESS파일이 변경될 때마다 CSS가 다시 생성되도록 watch Flag를 걸어줄 수도 있고 less.js를 이용해 웹 페이지에서 동적으로 CSS를 생성할 수도 있습니다. 저는 앞의 방법을 이용하는데 퍼블리셔의 경우는 뒤의 방법을 이용해 LESS를 작성하고 생성된 CSS를 개발자에게 전달하는 방식을 사용할 수 있습니다.

jQuery

jQuery jQuery는 Slide, Fade 등의 효과나 Ajax를 적용하기도 쉽지만 기본적으로 raw javascript로 작성했을 때보다 코드량을 획기적으로 줄여줍니다. 또한 각각의 함수들이 다양한 브라우져에서 동일하게 동작하도록 오랜 기간 테스트/개발되었기 때문에 javascript를 직접 작성했을 때보다 크로스브라우징에서의 안정적인 동작이 보장됩니다. 기존의 javascript량에 놀랐는데 동일한 기능을 하도록 jquery 함수를 이용했을 때 1/5 이하로 코드량이 줄어들고 코드 가독성이 향상됩니다. Rails 3.1부터는 prototype.js 대신 jQuery가 디폴트가 된다는 소식이죠. 과거에는 Rails에서 RJS 헬퍼 메서드 형태로 ajax를 이용했던 반면 최근에는 js.erb, js.haml 등으로 직접 jQuery/Prototype.js framework을 이용해 헬퍼 없이 직접 ajax를 핸들링하도록 권장하고 있습니다.

Layout, Partial

웹 프로그래밍은 인터페이스 부분이 크다보니 뷰 코드가 많아질 수밖에 없습니다. 뷰 코드만 줄여도 개발 생산성이 획기적으로 향상됩니다. Header, Footer를 포함해 Asset을 include하는 부분은 Layout에 넣고 자주 사용하는 뷰 코드 조각은 Partial 뷰로 정리합니다. Partial 뷰에 변수를 넘길 수 있기 때문에 Rails에서는 동일한 형태의 뷰 코드를 두 번 이상 작성할 일이 거의 없습니다. 특히 Ajax로 특정 부분이 비동기적으로 업데이트 되는 경우 해당 부분에 Partial뷰를 채우도록 처리함으로써 과거 Ajax에서 가장 까다로웠던 뷰의 동적인 생성을 쉽게 할 수 있게 됩니다.

Capistrano

위의 요소들이 개발 생산성을 높여주었다면 Capistrano는 배포 효율성과 Production 환경에서의 안정성을 확보해 줍니다. 웹서버, 앱서버, DB Master/slave를 따로 지정해 줄 수 있고 소스코드 저장소 위치와 배포시 별도로 실행되어야 하는 스크립트 등을 지정하면, 소스코드 수정 후 커맨드 하나로 실서비스 배치가 가능해집니다. 또한 설서비스 안에서 디렉토리 구조를 current, releases, shared로 분리하고 있는데, releases 폴더에는 매번 배포되는 소스코드를 버젼별로 관리하고 current는 최종 배포된 releases 폴더로 symlink만 걸려 있으며 웹서버는 이 symlink를 가리키도록 되어 있습니다. 만약 최종 배포본에 문제가 발견될 경우 rollback 커멘드를 통해 이전 release 버젼으로 심링크가 걸리게 되어 있어 안정적으로 배포할 수 있습니다. 소스코드와 별개로 각 release가 공유해야 하는 소스코드 이외의 리소스(사용자 업로드 이미지 등) 및 로그 파일은 shared에서 관리하도록 되어 있습니다.

Git

Git은 최근 각광받고 있는 SCM(Source Code Management) 툴로 리누스 토발즈가 개발에 참여했죠. SVN은 원격 저장소에서 로컬 개발환경으로 Checkout하고 수정 후 원격으로 commit 하는 형태인데 Git은 원격 저장소에서 로컬 저장소로 Clone (저장소 복사)를 하고 로컬에 수정 후 로컬에 먼저 커밋, 그 후에 Push(원격 저장소로 로컬 저장소의 변경 내용을 밀어넣는다는 개념)하게 됩니다. Rails와 Ruby와 관련된 오픈소스 프로젝트들은 대부분 소스코드가 Github에서 관리되고 있기 때문에 익숙해지는 것이 좋습니다. Github에 올라온 Ruby/Rails 관련 프로젝트들은 퀄리티도 높아 비즈니스적으로 활용하기도 좋습니다. 체감하기로는 SVN에 비해 원인불명의 충돌이 덜 발생하는 것 같고, 저장소의 분산이라는 설계 특성과 branching에 대한 기본적인 지원 덕분에 Git을 한 번 사용하면 SVN으로 돌아오기가 쉽지 않습니다.

Passenger

Passenger는 이제 대중화된 Rails Deployment 환경입니다. 개인적으로 자주 사용하는 스택은 Ubuntu-Nginx-Passenger + Mysql 입니다. Passenger는 Nginx와 Apache에 모듈로 붙어서 동작하는데 특히 Nginx는 퍼포먼스도 좋고 세팅도 간결합니다. Passenger와 Nginx 모두 무료임에도 불구하고 상당히 적극적으로 버젼업 하고 있기 때문에 퍼포먼스가 지속적으로 좋아질 거라 기대하고 있습니다. 또 과거에는 유료로 제공했던 Ruby Enterprise Edition을 이제는 오픈소스로 배포하고 있습니다. 기존의 Ruby Language보다 Memory Usage 효율을 높였다는데 다음 프로젝트에서 한 번 적용해봐야겠네요

Process

관리자 CMS는 내부적으로 이용하기 때문에 상대적으로 디자인 퀄리티에 대한 요구 수준이 낮았습니다. 이미지 사용을 최소화하고 퍼블리싱과 서버 개발을 단일화 하였기 때문에 JSP로 1년 동안 개발되어진 프로젝트를 2개월 내에 Rails3로 전환+기능개선+모듈화할 수 있었습니다. Rails 기반으로 생산성이 확보되자 운영 조직에서 요청하는 내용들이 거의 분 단위로 처리되고 처리 속도가 빨라지자 Framework에 관심을 가지기 어려웠을 운영 조직에서 조차 Rails 도입을 반기는 상황입니다. 위에 나열한 키워드들은 개발 관점에서 작성한 것이지 비즈니스 관점에서는 처리가 빠르고 버그가 적고 개발 조직이 협조적이라는 사실이 더 중요하죠.

지난 주부터 사용자 CMS에 대한 개편에 들어갔습니다. 관리자 CMS 개발 과정에서 잘 모듈화된 것들을 가져다 조립만 하는 되는 상황인데, 기획, 디자인, 개발에 이르는 프로세스에 변화를 주기로 했습니다. 기존의 방식은 아래와 같았습니다. 전 단계가 끝나면 다음 단계로 가는 형태입니다.

요구사항 정리 및 구현가능성 논의 –> 기획서 작성 –> 디자인 작업 –> 퍼블리싱 –> 서버 개발 –> 테스트

새롭게 시도할 방식은 아래와 같습니다.

요구사항 정리 및 구현가능성 논의 <-> Rough 퍼블리싱 / 서버 개발 <-> 디자인 / Detailed 퍼블리싱

기존의 방식이 일방향 이라면 새로운 방식은 (개발/피드백 사이틀 + 지속적인 보완) 형태입니다. 기존의 것보다 개선하고 사용해보고 또 개선하고 어느 정도 기능과 UX가 안정화 되기 전까지는 심미적인 디자인 요소들을 확정하지 않고 Rough하게 작업하다가 UI와 기능적인 요소들이 확정되면 포토샵 터치가 포함된 디자인 요소들을 CSS 형태로 최종 보완하는 방식입니다. 개발 부분에서는 퍼블리셔 작업물이 완성될 때까지 대기할 필요가 없고, 초기 기획 단계부터 베타버젼 제작에 들어가며, 가능한 일찍부터 기능에 대한 피드백을 받기 시작합니다.

기획 조직에서 기획서를 처음부터 Fix하기 보다는 기획, 디자인, 개발 조직이 모여 화이트보드에 같이 그려가면서 사용성과 생산성과 심미성이 동시에 확보되는 방안을 같이 논의하게 됩니다. 생산성이 확보되지 않은 개발 환경에서는 빈번한 수정이 스트레스이지만 생산성이 확보된 개발 환경에서는 Agile한 개발 사이클이 즐거운 지적 도전입니다.

blog comments powered by Disqus