Introduction
WebRSSAggregator는 RSS들을 수집, 통합하여, 웹을 통해 보여주는 어플리케이션이다. 즉, 흔히 말하는 RSS reader다. (전에도 이 사이트를 통해서 소개한 적이 있었으므로 더 자세한 내용은 생략하겠다.)
소프트웨어 개발의 진행 결과는 관리자에게 보고하고, 구현을 마친 요구 사항은 적어두면서, 개발을 하면서 발생한 design decison들을 기록하는 사람들은 거의 없다. 사실, 개발자의 실력을 키워주는 것은 그러한 작은 design decison이 모여서 이루어진 어떤 sense라고 생각한다. 이러한 과정은 물론 우리들의 뇌 속에서 자동으로 이루어지는 것이지만, 소프트웨어 개발을 하면서 드는 생각들을 정리해두는 것만으로도, 더욱 효과적으로, 긍정적인 피드백을 주리라고 생각한다.
Iteration 3
최근에는 적절한 feature들의 set을 모아서 기간을 정하고, 이를 하나의 iteration으로 개발하는 방식을 주로 사용하고 있는데, 이는 FDD에서 어느 정도 따온 방식이라고 볼 수 있다. WebRSSAggregator도 어느덧 세번째 iteration에 접어들었고, 이 iteration에서 구현할 feature들은 다음과 같다.
- Refactoring
- Model-View-Controller
- Apply eruby template
- Get method accepts Filter (rather than several get* methods)
- User Interface
- Change view (flat, simple)
- Change filter (subscription, category, date, label)
- mark/unmark Label
- More subscription information on subscription list, admin interface
- “Mark read”
- Content folding
- Feature
- Label operation: mark, unmark
- More subscription information
- retrieve channel information on addSubscription (title, link, description)
- automatically update feed on addSubscription (only for Web interface?)
- Documentation
- rdoc documentation
- Release
- commit rubyforge
여러가지로 적어두었지만, 사용자 인터페이스를 정리하고, 코드를 MVC 기반으로 refactoring하는 것이 이번 iteration의 주요 목표다. 그리고, RubyForge에 WebRSSAggregator를 release하는 것이 이번 iteration의 최종 목표다.
View
어느 정도 기능들이 많아지고 사용자 인터페이스가 복잡해지면서, 가장 문제가 되는 것이 view와 logic의 분리다. 웹 어플리케이션의 View를 어떻게 설계할 것인가를 고민 중이다. MVC에 따르면, 여러 sub-view들 간에 hierarchy를 가진 view 구조를 선호하고 있다. 따라서, 각각의 subview를 (div element를 사용한) block으로 만들고 어플리케이션에 특화한 layout을 가진 main view가 이들을 조직화하고, 대부분의 formatting은 css에 맡김으로써, 간단한 (적어도 WebRSSAggregator에는 사용 가능한) View 정도는 깔끔하게 만들어낼 수 있지 않을까 생각하고 있다.
당장은 subview들 약간만 작업해놓은 상태다. (SimpleItemListView, FlatItemListView) 한가지 고민되는 것은 우리나라의 여러 portal에서 즐겨쓰는 table을 사용한 html page design에는 이러한 방식이 최악이 될 수도 있다는 것이다. 아직도 table의 div 대체 가능성에 대해서 명확한 대답을 보지 못했고, 가능하다면 이것을 증명해보고 싶기도 하다. (물론 WebRSSAggregator 따위로 될 리는 없지만)
이미 Rails 같은 MVC framework이 널려있고, MVC pattern이 꽤 흥행해왔기 때문에 Web App에서의 View에 관련된 내 고민들은 이미 대체로 해결된 별 쓸모없는 것일지도 모른다. 좀 더 살펴볼 예정이고, 조언도 환영한다. 어쨌거나 흥미로운 문제고, 이것 때문에 Web App Development에 대한 나의 관심이 요즈음 한껏 고양된 상태이다.
Accessing DB
Filter
DB로부터 data를 얻어올 때, business logic이 요구하는 내용에 따라 여러 method가 난립했었는데, filter라는 개념을 도입하여, 단순하게 정리했다. 예를 들어, 기존에는,
ItemManager
getItemsBySubscription(subscriptions)
getItemsByDate(dates)
getItemsByLabel(labels)
이었으나, filter를 도입한 후에는,
ItemManager
getItems(subscriptionFilter, dateFilter, labelFilter)
과 같이 단 하나의 method로 정리되었다. 참고로, Rails에서는 SQL 문의 WHERE 절에 들어가는 expression과 유사한 string을 filter로 사용한다.
ItemManager
getItems(filterString)
이러한 filter들을 사용자 interface로 정리하는 것은 꽤 괴로운 일이었다. 직접 사용자의 선택을 모두 case by case로 처리하고 있는데, 다른 MVC framework의 Controller 디자인을 참조해보아야할 듯 하다.
Table Data Gateway/Active Record/Data Mapper
가장 처음 WebRSSAggregator를 만들었을 때는 class가 하나도 없었고, 단지 DB로 SQL 문을 query해서 data를 읽어온 후, html로 번역해서 뿌려주는 ruby script였을 뿐이었다. 초기 기능을 만들고나서 바로 class로 refactoring을 수행했고, 가장 먼저 건드린 곳이 DB를 access하는 부분이었다. 현재의 모습이 그 때 refactoring의 결과인데, 그러한 설계를 Table Data Gateway라고 부른다는 사실을 오늘 처음 알았다.
간단히 설명하면, DB table에 있는 data에 대한 모든 operation (주로 CRUD와 SELECT)을 수행하는 object다. 물론 이러한 operation들은 보통 business logic의 requirement에 따라 만들어지고, 각 operation이 보통 하나의 SQL 문을 나타낸다고 볼 수 있다.
WebRSSAggregator에서 ItemManager, SubscriptionManager, 그리고 CategoryManager가 모두 Table Data Gateway에 속한다. (‘Manager’라는 너무나 보편적인 이름을 지은 것은 후회해야할 일일 것 같다.)
ItemManager, SubscriptionManager, CategoryManager는 각각 attribute만을 가진 Item, Subscription, Category를 사용한다. 즉, Table Data Gateway는 passive data를 다루는 Controller object에 해당하는 것이다. “Object Thinking”에서는 이러한 설계가 Object-oriented paradigm의 철학과는 배치된다고 적극 말리고 있고, 이에 대한 대안을 찾고 있었는데, Rails의 문서를 뒤지던 중에, Active Record라는 pattern을 발견했다.
Active Record는 간단히 말해서, Controller object가 passive data들을 control하는 것이 아니라, Table 또는 Table의 row에 해당하는 object가 스스로 DB access를 수행하는 설계를 가지고 있다. 앞에서 말했던 “Object Thinking”에서의 충고에는 충실한 편이다.
당장은 Table Data Gateway인 ItemManager, SubscriptionManager, CategoryManager의 responsibility를 현재는 passive data에 불과한 Item, Subscription, Category로 이전시켜, Active Record로 만들 생각이다.
DB Access에 관련된 pattern에는 Table Data Gateway와 Active Record외에도 Data Mapper가 있다. Data Mapper는 object와 relational database 사이에서 OR mapping을 수행해주는 object다. 따라서, 이 경우 object는 DB Access에 관해 신경을 쓰지 않을 수 있게 된다. business logic과 DB를 access하는 logic을 적극적으로 분리하는 pattern이라고 볼 수 있다.
기회가 된다면 다음에 이러한 3가지 pattern에 대해서 자세히 써보겠지만, 이 글에서는 더이상 설명하지 않겠다. 좀 더 자세한 내용을 알고 싶은 사람은 Martin Fowler의 “Patterns of Enterprise Application Architecture”를 보는 것을 추천한다.
Embedding code in HTML
PHP programming language에 대한 어떤 비판 중에는 HTML 안에 code를 embed할 수 있기 때문에, presentation과 code의 분리를 방해한다라는 주장이 있다. asp, jsp 등이 web language로서 보편화된 것을 보면, 그 주장이 사실이라고 하더라도, 분명 그러한 단점을 넘어서는 장점이 있기때문일 것이다.
개인적인 생각으로는 code를 HTML에 embedding할 수 있는 것은 매우 편리하다. 위에서 살짝 언급했듯이, subview들을 block등으로 적절히 나타낸다고 하더라도, 전체 layout을 나타낼 때 code를 사용해 HTML을 generation한다면 매우 이해하기 힘들고 수정하기도 힘든 코드만 대량으로 생산해낼 뿐이다. 하지만, code embedding과 HTML을 적절히 조화시키면 깔끔한 View 코드를 생성할 수 있다.
<% require "MenuView.rb" require "ContentView.rb" menuView = MenuView.new contentView = ContentView.new %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> </head> <body> <div class="menu"> <% menuView.render %> </div> <div class="content"> <% contentView.render %> </div> </body> </html>
물론, 이러한 기능을 남용하는 사례는 분명 있다. 특히 sub-view가 아닌 business logic이 있을 code에 HTML들이 함께 버젓이 들어가있는 경우는 정말 난감하다.
<? function get_beer_list() { $beers = array ( "ob", "hite", "cass" ); ?> <ul> <? foreach ($beers as $beer) { ?> <li> <? print $beer; ?> </li> <? } ?> </ul> <? } ?>
이러한 코드를 보면 그러한 비판은 정말로 유효한 것 같다. (실제로는 더욱 심할테니!) 하지만, 이것은 언어 자체의 문제나, HTML에 code를 embedding 시킬 수 있는 기능 자체의 문제가 아니다. 이러한 코드를 쓴 사람은 어떤 프로그램을 짜더라도 엉망으로 짤 것이다. 프로그래머로서의 자질 자체가 문제인 것이다.
WebRSSAggregator에도 이러한 생각을 증명하기 위해서 eruby를 적용해보았다. 현재까지 만들어진 sub-view들을 제외한 View는 전부 HTML과 embedding된 ruby code로 되어있다. 가능한 한 대부분의 View는 클래스화 하겠지만, 전체 페이지 모두를 클래스화할지는 아직 의문이다.
To Be Continued…
WebRSSAggregator에서의 MVC 구현이 좀 더 진척된 미래의 어느 시점에 다음 편이 나오지 않을까 싶다.