작년 말부터 준비한 토비의 스프링 3 증보판이 마무리되서 지난 주말부터 인터넷 서점에서 예판을 시작했다. 실제 출간일은 이달 말이다.

이번 증보판은 스프링 3.0을 기준으로 설명한 토비의 스프링 3 내용에 스프링 3.1내용을 200여페이지 추가한 것이다. 덕분에 책을 두 권으로 분리해야 했고 두권을 합쳐 전체 페이지 수가 1700이 넘게 되었다. 분책을 하다보니 각 권만 선택해 책을 보는 독자들의 편의를 위해 앞의 부속글과 부록, 찾아보기 등이 양 권에 모두 들어가서 페이지 수가 좀 더 늘어났다.

Vol1은 예전 1부 내용에 user 예제 코드를 스프링 3.1의 DI 스타일로 업그레이드 하는 과정과 그 특징을 설명하는 내용을 추가했다. Vol2는 예전 2부의 스프링 기술 설명에 각 장마다 스프링 3.1에 새로 등장한 내용을 다루는 절을 추가했다. 스프링 3.0 내용도 일부 추가된 것이 있다. 3.0.3이후에 나온 mvc 네임스페이스의 일부 태그에 관한 설명이 MVC를 다루는 장에 추가됐다.

책이 두 권으로 분권됐기 때문에 낱권으로 구매가 가능하다. 물론 세트로 구입할 수도 있다.

그 밖에 하고 싶은 얘기가 정말 많긴 한데… 이번엔 그냥 여기까지만.

이제 드롭박스 없이 일하는 건 상상하기도 힘들다. 사내 파일서버, 이메일과 FTP 등으로 자료를 공유하고 주고 받던 고객에게 드롭박스를 소개해주니 몇 주만에 모든 업무가 다 드롭박스를 기반으로 바뀌는 것을 볼 수 있을 정도로 실무자들 사이에서도 인기가 대단하다. 직관적인 인터페이스와 안정적인 서비스, 회사 업무용 파일 정도를 주고 받기에 충분히 빠른 속도, 다양한 미디어 지원 등등.

프로젝트를 진행하면서 관련된 분석자료나 참고할 정보, 각종 설계문서 등을 드롭박스를 통해서 고객과 공유하던 중에 새로운 용도를 하나 발견했다. 서버에서 동작하는 시스템과 자료를 주고 받는 데 사용하는 것이다.

재작년부터 진행했던 프로젝트에서 회계관련 업무를 지원하기 위해서 은행의 기업용 뱅킹 시스템과 연동하는 기능이 필요했다. 단순 트랜잭션이 아닌 특별한 서비스라 그런지 호주 은행의 IT 기술의 발전이 더뎌서 그런지는 모르겠지만, 아무튼 해당 뱅킹 업무를 은행 시스템과 자동으로 연결해서 처리할 방법이 없었다. 그래서, 사람이 직접 기업용 인터넷 뱅킹 사이트에 들어가서 여러 단계의 OTP 보안을 거쳐서 필요한 정보 파일을 다운로드 받아오거나 업로드하는 작업이 필요했다. 파일 포맷은 15년 전에 은행 프로젝트 할 때나 다뤄봤던 통짜 전문이었다. 은행에 요청할 뱅킹 작업이 있으면 시스템에서 파일을 다운로드 받아서 기업용 뱅킹 사이트에 업로드 하고, 다음날 아침에 해당 요청의 처리결과를 역시 파일로 받아서 시스템에 다시 업로드하는 식으로 구성할 수 밖에 없었다

하루에도 여러번 은행 시스템에 들어가서 파일을 받아오고, 다시 사내 시스템에 로그인해서 업로드하고, 또 그 반대 작업을 반복해야 하느라 담당자가 소모하는 시간이 적지 않았다.

은행 시스템이야 어떻게 손댈 수 있는게 아니라 어쩔 수 없지만, 내가 개발했던 고객사 시스템은 뭔가 개선할 방법이 있지 않을까 고민을 해봤다. 그러던 중에 드롭박스를 이용해서 편리하게 파일을 주고 받는 것을 서버와 담당자 사이에도 적용할 수 있지 않을까하는 생각이 들었다. 그래서 찾아보니, 리눅스 서버에서 돌아가는 드롭박스 서비스가 있었다.

https://www.dropbox.com/install?os=lnx

여기에 나온 Dropbox Daemon을 이용하면 리눅스 서버에서 드롭박스를 돌릴 수 있다.

드롭박스를 이용하겠다고 생각하니 그 다음은 간단했다.

서버 시스템용 드롭박스 계정을 하나 만들고 커맨드 라인용 드롭박스 서버를 설치한 뒤에 계정을 연결했다. 그리고, 업로드용 폴더와 다운로드용 폴더를 각각 생성하고 고객사 담당자의 드롭박스 계정과 공유하도록 했다.

시스템에서 은행 업무가 필요한 시점이 되면 필요한 파일을 생성해서 드롭박스의 다운로드 폴더에 넣도록 만들었다. 그러면 담당자의 드롭박스 폴더에 파일이 들어가고, 이를 뱅킹 시스템에 업로드 해주고 폴더에서 삭제한다.

반대로 은행에서 받아서 시스템에 업로드할 파일이 있으면 담당자가 시스템 업로드용 드롭박스 폴더에 파일을 넣어주기만 하면 된다. 파일은 서버의 드롭박스 폴더에 들어갈테고, 이를 서버 시스템이 가져와 처리하도록 만들었다. 새로운 파일이 생성되는 것을 인식하는 데는 jpatchwatch(http://jpathwatch.wordpress.com/)를 이용했다. 성공적으로 처리된 파일은 백업해두고 공유 폴더에서는 삭제한다.

시스템에 들어가 파일을 다운로드 하고, 업로드하는 작업을 드롭박스 폴더에 파일을 넣고 가져오는 것으로 대체한 것이다.

서버가 새로운 파일을 생성했거나 업로드된 파일을 처리했을 때의 결과를 알리는 데는 메일이나 메신저, SMS를 이용했다. 정해진 시간 내에 통보가 오지 않고 업로드용 폴더에서 파일이 제거되지 않으면 뭔가 서버가 제대로 처리하지 않았다는 것을 알 수 있다. 물론 개발하면서 테스트할 때 빼고는 지난 2년간 그런 문제는 발생하지 않았다. 기대 이상으로 안정적으로 동작하는 편리한 환경을 구성할 수 있었다.

그 외에도 개발이나 서버관리, 유지보수를 위해서 관련 파일을 가져오거나 올릴 때도 ftp나 scp 등을 사용하는 대신 개발용으로 따로 생성한 드롭박스 폴더를 이용하니 매우 편리하다.

드롭박스 없을 땐 어찌 살았나 상상이 안되네.

http://www.infoq.com/presentations/How-We-Mostly-Moved-from-Java-to-Scala

오래 전에 들은, 36만 라인의 EJB 코드로 작성됐던 프랑스 정부의 세금신고 시스템을 단계적으로 스프링으로 전환해서 3만라인의 깔끔한 코드로 만들었다는 이야기 다음으로 흥미로운 내용이군. 유사한 점도 많다.

프로그래밍 언어와 런타임 플랫폼의 전환은 애플리케이션 프레임워크 전환과는 비교도 안될만큼 큰 비용이 든다. 언어의 패러다임까지 바뀐다면 말할 것도 없고. 매 프로젝트마다 작은 시스템을 바닥부터 다시 만들어도 그만인 소규모 팀이라면 모를까. 장기간 개발해왔고 운영중인 미션 크리티컬한 시스템을 운영중인 채로, 인력 풀도 그대로 두고, 단계적인 전환을 해야 한다면 그에 맞는 치밀하고 유연한 전략이 필요할 것이다.

기존 기술과 플랫폼을 유지한 채로 작은 변화를 만들고, 언어의 변화도 일단 기존 언어를 개발하던 습관을 크게 바꾸지 않은 채로 서서히 전환하고, 시간을 두고 발전시켜 나간 것이 성공의 이유가 아닐까 싶다.

스칼라를 세미콜론 없는 자바라고 생각하고 사용하기 시작하는 것과 같은 용기가 필요하다. 오덕들이 몰려와 비아냥 거리기 딱 좋겠지만. 뭐 어때. 자바를 자바답게 쓰기 시작한 건 얼마나 됐나.

자기 잘난 거 증명하려고 코딩하는 해커가 아니라면, 개발자는 적절한 기술을 이용해서 고객의 문제를 해결하고 가치를 만들어내는 것이 가장 큰 관심이어야 할 것이다. 그런 면에서 스칼라와 같은 많은 장점이 있는 언어에 관심을 가지는 것도 필요하고, 동시에 욕심을 내지 말고 무식한 방법처럼 보이더라도 작은 단계를 거쳐서 변화를 시도하는 것도 중요할 듯.

제대로 적용할 기회도 못 만들면서 이런 저런 언어를 찝적거리는 것은 그만하고 스칼라에 올인해볼까. 흠흠.

(이미지는 yes24)

내가 처음 프로그래밍에 빠지게 된 건 국민학교 5학년 때 친구집에 놀러갔다가, 당시 SPC-1000 홍보용으로 삼성전자가 뿌려대던 컴퓨터 소개 만화책을 발견하고서부터이다. 책 내용은 CPU가 어떻게 동작하고 RAM은 뭐하는 놈이고 주변기기가 어떻고 하는 내용이었다. 그런데 뒤로 가면 BASIC이라는 놈을 이용해서 두 개의 정수를 입력 받아서 더하기 문제를 내고, 답을 넣으면 맞았는지 틀렸는지를 알려주는 간단한 프로그램을 작성하는 방법이 나와있었다. 10 INPUT A. 뭐 이렇게 시작하는. 프로그램이라는 것을 만들면 컴퓨터가 내가 원하는 대로 움직여준다는 것이 마냥 신기했다. 그리고 그 책을 빌려와서 한 100번쯤 읽은 뒤 나도 제대로 프로그래밍을 배워고보 싶다는 생각을 했고 세운상가와 동네 PC매장, 교보문고 종로 출입구 바로 옆에 있던 컴퓨터 섹션을 싸돌아 다니며 코드가 나와있는 책을 구해서 공부하다가… 결국 이 모냥이 됐다. 아무튼 그때 친구네 집 방구석에서 굴러다니던 그 책을 만나지 않았다면 나도 대부분의 친구들 처럼 막연히 컴퓨터=게임기라고 생각하며 컸을지도 모르겠다.

며칠 전에는 트위터를 뒤지다가 인사이트 출판사에서 파이썬 어쩌고 책을 ebook으로 낸다는 얘기를 발견했다. (남이 만든) 소프트웨어와 (남이 쓴) 이북에는 돈을 쓰는게 아니라는 신념을 가진 개발자가 적지 않을텐데 특정 기기 전용도 아니고, DRM도 없는, 친구나 동료와 살며시 돌려보거나, 토런트 사이트에 올라가기 딱 좋은 PDF 포맷으로 프로그래밍 책이 나온다는 것이 놀랍기도 하고 한편으로 걱정이 들기도 했다.

출판사의 이런 시도가 "역시나"로 끝나지 않았으면 하는 마음이 들어서 나도 이북을 바로 구입했다. 사실 파이썬에는 별 관심이 없다. 파이썬은 97년에 C++보다 10배 생산성이 좋다는 홍보성 글에 넘어가 공부하고, 업무에 적용했다가 프로젝트를 말아먹을 뻔한 경험이 있다. 그 뒤로 정 떼고 있었는데 몇년 전에, 지금은 홍공과기대 교수로 있는 성훈이가 같이 관여했던 프로젝트의 CMS로 Plone을 쓰자고 우겨서 얼떨결에 설치했다가, 툭하면 쏟아져 나오는 괴상한 파이썬 에러메시지와 씨름했던 악몽도 있다. 레딧 같은 커뮤니티를 돌아다니면 파이썬 좀 한다고 다른 언어(주로 자바와 php) 사용자를 무시하고 낄낄거리며 놀려대는 저질 오덕들도 많이 만난 탓에 갈수록 정 떨어지는 언어였다. 그런데 생각지 않았던 이북을 구입한 탓에 다시 파이썬을 공부해봐야 할 것 같다. 까짓거 잡아 먹히기야 하겠어.

홍보용 만화책을 우연히 만난 덕에 프로그래밍의 재미에 빠졌던 것처럼 이 책과의 만남이 생각지 않았던 또 다른 재미에 빠지게 해줄지도 모르겠다.

참, 이걸 공부하면 스프링 파이썬(http://springpython.webfactional.com/)을 써볼 수도 있겠구나.

아무튼 읽기 시작.

앞으로 한글로 된 프로그래밍 이북이 많이 나오기를 바라는 마음이 있다면 여기가서 한 권씩 사보기를.

성윤님이 코드로 등록해 로그를 찍어 공개하는 바람에 김이 빠져버린 퀴즈. 원래 문제만 보고 푸는 퀴즈였는데…

어쨌든 답을 정리하고 설명해보자. 매번 로그 찍어가며 원하는 요청 조건을 찾아낼 수는 없으니까 각 요청조건의 적용 규칙을 알아야지.

@RequestMapping의 각 항목은 RequestCondition 타입으로 만들어진 요청조건 클래스로 정의된다. @RequestMapping의 엘리먼트 타입은 모두 배열이니 {}를 이용해 복수개 설정이 가능하다. 또, 타입레벨과 메소드 레벨 양쪽에 정의가 가능하다. 최종 적용은 메소드 단위이니 타입 레벨의 조건과 어떻게 결합되는지 알아야 한다. 여기에 수퍼 클래스나 인터페이스의 타입&메소드 레벨 조건도 결합할 수 있으니 단순하게 봐도 조합 방법이 엘리먼트 값 여러 개 * 메소드&타입 * 수퍼 타입&메소드 해서 최소 8가지. 문제는 요청조건마다 엘리먼트 값의 조합이나 메소드&타입 조합방법이 다르다는 것. 

문제를 살펴보자.

1.
@Controller
@RequestMapping({“/a”, “/b”})
public class MyController {
  @RequestMapping({“/c”, “/d”})
  public void hello() {}

이건 URL경로 조건. 디폴트 엘리먼트라 value로 해도 되고, 단독으로 사용되면 엘리먼트 이름은 생략 가능.

각 엘리먼트 값의 조합은 OR. 메소드만 놓고 보면 /c 또는 /d.

타입과 메소드의 조합도 OR이지만 단순히 a,b,c,d로 조합하는게 아니라 타입 경로+메소드 경로로 결합 가능한 모든 경우로 조합한다는 것이 특징이다. 결국 /a/c, /a/d, /b/c, /b/d의 네 가지 조합이 만들어지고 OR로 결합되니 이 중의 하나면 된다. AND라면 말이 안되니까. 한가지 주의할 점은 다른 조건과 다르게 URL을 조건을 지정안 한다고 조건을 다 만족하는게 아니다. URL조건이 없으면 ""인데 suffix / 규칙 때문에 "/”나 마찬가지다. 결국 서블릿 매핑의 루트 URL에 연결된다.

최종 요청조건은 {[/a/c || /a/d || /b/c || /b/d], methods=[], params=[], headers=[], consumes=[], produces=[], custom=[]} 으로 표현된다. 스프링 로그를 찍어보면 조합된 조건이 이렇게 간결한 식으로 나온다.

2.
@Controller
@RequestMapping(params={“a”, “b”})
public class MyController {
  @RequestMapping(params={“c”, “d”})
  public void hello() {}

파라미터 조합은 AND다. 엘리먼트 값들도 AND로. 타입, 메소드도 AND로 따라서 최종 조건은 한 가지다. 위의 매핑이라면 파라미터에 a,b,c,d 네 가지가 다 존재해야지만 조건 만족.

조건식은 {[], methods=[], params=[a && b && c && d], headers=[], consumes=[], produces=[], custom=[]}

3.
@Controller
@RequestMapping(headers={“a”, “b”})
public class MyController {
  @RequestMapping(headers={“c”, “d”})
  public void hello() {}

헤더 조건도 파라미터와 동일하게 타입, 메소드, 엘리먼트 값이 모두 AND로 조합된다. 매핑 가능한 요청조건은 한 가지.

조건식은 {[], methods=[], params=[], headers=[a && b && c && d], consumes=[], produces=[], custom=[]}

여기까지는 쉬운데 다음부터는 좀 정확한 이해가 필요하다.

4.
@Controller
@RequestMapping(headers={“a”, “Content-Type=application/json”, “Content-Type=multipart/form-data”})
public class MyController {
  @RequestMapping(headers={“c”, “d”})
  public void hello() {}

타입에 헤더 조건이 3개, 메소드에 2개다. 헤더 조건은 AND 조합이라고 했으니 결국 조건은 한 가지?

아니다. 두 가지다. header로 정의했더라도 Context-Type은 무시된다. 헤더 조건은 a,c,d 세 가지만 적용된다. Content-Type은 대신 3.1에 새로 등장한 consumes 조건으로 바뀐다. 따라서 타입에 붙은 두 개의 Content-Type 헤더 조건 두 가지는 consumes에 정의된 것으로 취급되는데, consumes는 헤더 값을 이용하지만 다른 헤더와 달리 AND가 아니라 OR조건이다. 따라서 헤더 조건 3가지가 AND로 묶이고, Content-Type 두 가지는 OR로 묶여서 최종적으로 2가지 요청조건이 만들어진다.

Content-Type은 headers에서 무시하고 몰래 consumes로 처리하는 이유는 뭔가? headers에서 안 쓸 거면 에러를 내든가하지. 왜냐하면 3.0에서 작성된 코드를 3.1에서 사용될 수 있도록 하기 위해서다.  구버전 호환성을 목숨처럼 지키는 스프링 다운 해결책이다.

조건식은 {[], methods=[], params=[], headers=[a && c && d], consumes=[application/json || multipart/form-data], produces=[], custom=[]}

5.
@Controller
@RequestMapping(headers={“a”, “b”})
public class MyController {
  @RequestMapping(headers={“c”, “d”, “Content-Type=application/json”}, consumes=”multipart/form-data”)
  public void hello() {}

4번 문제의 응용이다. 일반 헤더 조건은 AND로 결합해서 한 가지. Content-Type헤더는 consumer로 넘어가므로 consumes가 결국 두 가지가 된다. consumes는 OR이니까 최종 요청조건은 두 가지.

조건식은 {[], methods=[], params=[], headers=[a && b && c && d], consumes=[application/json || multipart/form-data], produces=[], custom=[]}

6.
@Controller
@RequestMapping(consumes={“application/xml”, “application/x-www-form-urlencoded”})
public class MyController {
  @RequestMapping(consumes={”multipart/form-data”, “application/json”})
  public void hello() {}

consumes는 다른 조건과 다르게 메소드 조건이 타입 조건을 오버라이딩하는 식이다. 타입만 있으면 타입 조건을 쓰지만 메소드 조건이 있으면 타입 조건을 무시한다. 그래서 이건 메소드 조건 두 가지만 OR로 결합되서 두 가지.

조건식은 {[], methods=[], params=[], headers=[], consumes=[multipart/form-data || application/json], produces=[], custom=[]}

 

정답은 4,1,1,2,2,2

 

그럼 수퍼 클래스나 인터페이스의 타입&메소드 레벨에 붙은 @RequestMapping과의 조합은? 그건 http://toby.epril.com/?p=1207 에서 설명한 스프링의 일반적인 오버라이딩스러운 규칙을 적용하면 된다.

© 2014 Toby's Epril Suffusion theme by Sayontan Sinha