(loop (print (eval (read))))

;;닭집을 차리기 위한 여정

warless webapp 전략

이런 접근이 나온지도 벌써 수년이 흘렀고, 몇년동안 써먹고 있는 방법이긴하지만 새삼스럽게 공개하자면

http://www.jamesward.com/2011/08/23/war-less-java-web-apps 여기에 아주 잘 정리 되어 있다.

요점은, embedded jetty 를 통해 main 메서드를 통해 ide 로도 실행할 수 있고, excutable jar 를 생성하여 실행할 수 도 있는 프로젝트를 구성하는 것.

이 접근은 언뜻 생각하기에도 벌써 장점이 여럿 보인다.

  • ide를 통한 실행은 별도로 컴파일 하지 않아도 된다 (incremental compile)
  • 또한 웹 리소스 파일들은 main 메서드에 의해 직접 참조되므로 복사할 필요가 없다.
  • 따라서 프로젝트 초기라면 (또 어느정도 규모가 있지 않다면) 웹어플리케이션을 정지하고 재구동하는데 5초 미만의 시간이 걸리게 된다.

좀 더 작고 기민하게, 수수하지만 실용적인 방법으로 개발하고자 할 때 알맞겠다.

jetty는 사실 고민할 필요도 없이 좋은 was다.
https://wiki.eclipse.org/Jetty/Feature/Continuations

물론 가장 기민한 것은 clojure 다.
이미 jetty를 쓰고 있기도 하고..
하지만 clojure 는 돈이 안되므로.

마이바티스 활용하기

매핑하기.

사실, 모든 내용은 마이바티스 공홈에 다 있다. 그런데 흔히 잘 안보고 지나치는 기능 두개에 대해서 얘기해보려고 한다.
columnPrefix 와 discrimantor 가 그것이다.

이들을 써서 맵핑하는 방법이다.

1
2
3
4
5
6
7
8
9
10
11
<resultMap type="exp.model.Post" id="Post">
<id column="ID" property="id"/>
<result column="NAME" property="name"/>
<result column="TITLE" property="title"/>
<result column="CONTENT" property="content"/>
<association property="author" columnPrefix="USR_" resultMap="exp.repo.UserMapper.User"/>
<discriminator javaType="exp.var.PostType" column="type">
<case value="PUBLIC" resultType="exp.model.PublicPost"/>
<case value="PRIVATE" resultType="exp.model.PrivatePost"/>
</discriminator>
</resultMap>

association 엘리먼트에 걸린 내용을 잘 보면 columnPrefix 어트리뷰트에 USR_ 이라고 명시하고 다른 결과맵으로 맵핑하도록 해놓았다.
그리고 discriminatorjavaType 으로 enum 타입을 적시하고 어떤 타입으로 맵핑할 것인지 정한다. 물론 Post의 하위 타입이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<select id="select" resultMap="Post">
<![CDATA[
SELECT
post.ID,
post.TYPE,
post.TITLE,
post.CONTENT,
usr.ID AS USR_ID,
usr.TYPE AS USR_TYPE,
usr.NAME AS USR_NAME,
usr.AGE AS USR_AGE
FROM
post
JOIN
USR ON post.USR_ID = USR.ID
]]>
</select>

columnPrefix

테이블명과 자바클래스명을 일치시킨다면, comlumnPrefix를 이용해서 모든 alias 를 멤버명과 동일하게 일치시킬 수 있다. 예를들어,
Post 클래스에 comments 필드가 있다면 columnPrefixCOMMENTS_ 로 주고 collection 으로 매핑하면 된다.
(나는 이를 위해서라도 테이블명에 복수형을 붙이지 않는다. 테이블명을 복수형으로 붙이는건 (예를들어 users, posts..) 간지난다는거 빼고 좋은게 없는듯.

더 좋은것은 이 columnPrefix 가 선언된 하위 resultMap 은 재귀적으로 적용된다는 것. 예를들어 COMMENTS_AUTHOR_ID 같은 alias를 해석할수 있다는 것.

하지만 이 방법은 캐쉬를 적용하기에 좋지 않다.

discriminator

Post 클래스는 어쩔 수 없이 abstract 클래스여서는 안된다.
그런 제약이 있기는 해도, select 한 번으로 각 하위타입으로 매핑해서 (또한 재귀적으로) 조회할 수 있다는건 큰 매력이다.
참고로 이렇게 조회한 Post 의 리스트는 google-guava 라이브러리의 filter 메서드같은 함수형 접근과 아주 궁합이 잘 맞는다.

일전에 언급했던 템플릿 패턴과 같이 쓰면 표현력이 대폭 상승하게 된다.

서비스 레이어가 *정말로* 필요한가?

간단하게 할 수 있으면 간단하게 해라.

맞지 않나? 간단히 할 수 있는일을 왜 안 간단하게 하는지 모르겠다.
무슨 디커플링 어쩌구 하는데, 단 한번 쓰일 메서드 내지는 다음에 쓰일지 말지 애매한 메서드까지 왜 서비스 레이어 위에 올리는걸까?
왜 소스코드를 늘리고 더 복잡하게 만드는걸까? 이해가 안된다.
간단히 Authentication 만 봐도 그렇다. 인증 과정이 여러 포인트에서 지금 당장 일어나고 있다면 모를까, 최초 코드 작성시점에서 벌써 그런 사정을 고려할 필요는 없다.
이게 스프링 커뮤니티에서 추천하는 jdk프록시와 만나 impl 서비스 레이어를 만드는 것으로 더 복잡해진다.

LoginController -> LoginService -> LoginServiceImpl -> UserDao -> UserMapper

컨트롤러에 비즈니스 로직을 집어넣는것이 나쁘다 (내지는 바람직하지 않다) 고 하는게 널리 알려진 말이지만,
지금껏 경험한 바로는 그런 말이 이해가 안될정도로, 그저 똑같은 메서드를 가진 서비스 클래스를 하나 더 만들뿐이다.

어떤 개념을 하나 더 두는것이 아무리 일정한 컨벤션을 가지고 있고 나중에 쓸모가 있을지도 모른다 할지라도 가장 좋은건 그냥 없는게 가장 좋지 않나?

예를들면 인증은 그냥 이런 코드로도 충분하지 않나 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
@Controller
public class RootController {
@Resource(name = "session")
Session session;
@Inject
UserMapper userMapper;
@Inject
PostMapper postMapper;
@Inject
FooService fooService;
@RequestMapping("/")
public String root() {
if (session.getUser() == null)
return "redirect:/page/hello";
else
return "redirect:/page/home";
}
@Authorized(to = Type.PUBLIC)
@RequestMapping("/page/hello")
public String hello() {
return "/jsp/hello.jsp";
}
@RequestMapping("/page/home")
public ModelAndView home() {
List<Post> posts = postMapper.select();
return new ModelAndView("/jsp/home.jsp").addObject("posts", posts);
}
@Authorized(to = Type.PUBLIC)
@RequestMapping("/proc/login")
public String login(
@RequestParam(required = true) String name,
@RequestParam(required = true) String password
) {
User user = userMapper.selectByLogin(name, password);
if (user == null)
throw new NoSuchUserException();
session.setUser(user);
return "redirect:/";
}
@RequestMapping("/proc/logout")
public String logout() {
session.setUser(null);
return "redirect:/";
}
}

트랜잭션과 aspectj

유일하게 쓸만한..

그동안 쭉 cglib 프록시를 썼었다. 아무래도 cglib의 프록시 방식이 substance를 만드는 방식이다보니,
내부 메서드 호출에 대해서도 잘 감싸주겠지 생각했었는데 알아보고 실험해보니 jdk프록시와 다를바없이 동작하더라는..

예를들어 간단히 이런 코드를 돌려보면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Service
public class FooService {
@Inject
PostMapper postMapper;
@Inject
UserMapper userMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void insertFoo(User user, Post post) {
userMapper.insert(user);
insertBar(post);
throw new RuntimeException("fucking fucked!!");
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void insertBar(Post post) {
postMapper.insert(post);
}
}

예상과는 다르게 postMapper.insert(post) 마저 롤백되어 버린다.
실제로 브레이크 포인트를 걸어가며 디버깅해보면 프록시를 통하지 않고 다이렉트로 직접 호출하고 있다.
substance인데 왜 ㅅㅂ 존나 노이해..

결국 유일하게 쓸만한 솔루션은 그냥 닥치고 AspectJ 라는 거다. AspectJ 를 쓰면 아무런 문제가 생기지 않는다.
(혹은 cglib을 고쳐서 쓰거나…)

이렇게 동작하는것만 보장되면 구차하게 계속 서비스를 안 쪼개도 되고 템플릿 패턴을 쓸 수 있게된다.
생각해보면 컨트롤러와 서비스의 계층을 확실하게 나누는데도 도움이 된다. 예를들어 일반적인 처리는 컨트롤러에서, 특정 프로세스가 2회 이상 반복되는 경우에 템플릿 서비스로 계층화를 시키는게 가능하다. 이러면 소스를 줄이는데도 크게 도움이되고, 서비스== 템플릿 이므로 어떤 서비스가 어디서 호출되는지 고민할 필요도 없어진다.

Best Practice

이번에 이직준비를 하면서 한 회사로부터 면접 주제를 받았는데 바로 저 베스트 프랙티스에 대한 PT 였다.
주제도 주제이고, 정장입고 하래서 바로 포기했음.
여튼 이것에 대해서는 사실 기존에 하던 생각이 있어서 오랫만에 글을 적어봄.

무얼위한?

사전적 의미로는 모범사례. 하지만 이 IT하는 바닥에서는 여기에 여러 의미가 덧칠해져 마치 어떤 사상이나 종교 비슷한게 되버린다.
예를 들어 뭔가 할때 그것은 spring-way- 하지 안항요 라던가 엔터프라이즈 규격에 맞게 해주세요 라는 식.
좀 더 노골적으로 말하면 어떤 무리 가 있고 그들의 생각은 시간은 없고 일은 간지나게 해야겠으니 복사 붙여넣기 하여도 문제 없을만한 코드 가 바로 베스트 프랙티스 정도가 되는거시다.

도입부가 논리비약이 아주 심한데 뭐, 재밌으니까 계속 가봄.

Guice vs Spring

유명한 DI 프레임워크로 스프링과 구글쥬스가 있다. 자 어떤게 Best Practice 인가?

Autowire vs Inject

위에서 스프링을 선택했다면 또 선택지가 두 개 생긴다. 자 @Autowire@Inject 어노테이션 중 무엇이 Best Practice 인가?
참고로 자바 표준은 @Inject 어노테이션이다.

XML vs Java Config

자 스프링을 선택했다면 또 선택지가 있다. xml로 applicationContext.xml 파일을 만들것인가? 하는것.
참고로 구글쥬스를 선택했다면 autowire 니 xml 이니 하는것을 고민할 필요가 없다. 없으니까. 10년도 더 전부터.

jdk proxy vs cglib proxy

jdk 프록시를 선택할 것이냐 아니면 cglib 프록시를 선택할 것이냐?
바꿔말해 interface 기반의 di를 할껀지? class 기반의 di를 할껀지?
또 바꿔말해 runtime 에 proxy를 생성할건지 아니면 거의- compile-time에 bytecode 조작을 통한 subclass를 만들건지?
다시말해 근본주의 노선인지? 아니면 실용주의 노선인지?

myBatis vs hibernate

단순하고 반복되는 코드를 패턴화해서, 유지보수까지는 바라지도 않고 개발중 정신이 혼미해지는거를 방지하는것은 좋다.
하지만 java -> orm 노선은 현실적인 문제 몇가지를 해결하지 못하고 있다..
반면 myBatis는 존내 딱 자기 역할만하는데 그걸 참 잘함.

java vs clojure

그래서 말인데, orm 중 clojure 의 sqlKorma 같은게 정말 좋아보인다. 그냥 java 말고 clojure 만 쓰거나 두개를 같이 쓰는것은 어떤가?

그래서, 그래서…

적어도, IT하는 바닥에서 어떤 진짜 사상 (예를들어 OOP 같은거) 을 좀더 잘 드러내기 위한 Best Practice 같은건 있을 수 있다.
하지만 대부분의 사람들이 그걸 원하는지는 별개 문제임.
자신이 바라는게 복붙 가능한 코드라면 사실은 Best Practice 의 반대를 바라는게 아닐까?

마리아디비.. 뭐니?

뭔가 지금 이상한 일이 일어나고 있는거 가튼디..

새로 일을 시작해서 db로 마리아디비를 골랐다.
근데 요놈이 mysql하고 똑같다. 버그까지 -_-;
커넥션풀을 죽이는 wait_timeout 설정이 있어서 우회하고
같은 상황을 또 마주하게 됐다.

my.cnf
1
2
3
4
5
6
7
8
9
max_connections = 100
connect_timeout = 5
wait_timeout = 60
max_allowed_packet = 16M
thread_cache_size = 128
sort_buffer_size = 4M
bulk_insert_buffer_size = 16M
tmp_table_size = 32M
max_heap_table_size = 32M

어라? wait_timeout 이 60 이네?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MariaDB [(none)]> show variables like '%timeout';
+-----------------------------+----------+
| Variable_name | Value |
+-----------------------------+----------+
| connect_timeout | 5 |
| delayed_insert_timeout | 300 |
| innodb_flush_log_at_timeout | 1 |
| innodb_lock_wait_timeout | 50 |
| innodb_rollback_on_timeout | OFF |
| interactive_timeout | 28800 |
| lock_wait_timeout | 31536000 |
| net_read_timeout | 30 |
| net_write_timeout | 60 |
| slave_net_timeout | 3600 |
| thread_pool_idle_timeout | 60 |
| wait_timeout | 28800 |
+-----------------------------+----------+
12 rows in set (0.00 sec)

어라 여기서는 28800 이네?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
MariaDB [(none)]> show processlist;
+----+------+--------------------+----------+---------+------+-------+------------------+----------+
| Id | User | Host | db | Command | Time | State | Info | Progress |
+----+------+--------------------+----------+---------+------+-------+------------------+----------+
| 51 | root | localhost | NULL | Query | 0 | init | show processlist | 0.000 |
| 53 | xxx | xxx.xx.xx.xx:50258 | xxxxxxxx | Sleep | 6 | | NULL | 0.000 |
| 54 | xxx | xxx.xx.xx.xx:50259 | xxxxxxxx | Sleep | 6 | | NULL | 0.000 |
| 55 | xxx | xxx.xx.xx.xx:50260 | xxxxxxxx | Sleep | 6 | | NULL | 0.000 |
| 56 | xxx | xxx.xx.xx.xx:50261 | xxxxxxxx | Sleep | 6 | | NULL | 0.000 |
| 57 | xxx | xxx.xx.xx.xx:50262 | xxxxxxxx | Sleep | 6 | | NULL | 0.000 |
| 58 | xxx | xxx.xx.xx.xx:50263 | xxxxxxxx | Sleep | 6 | | NULL | 0.000 |
| 59 | xxx | xxx.xx.xx.xx:50264 | xxxxxxxx | Sleep | 6 | | NULL | 0.000 |
| 60 | xxx | xxx.xx.xx.xx:50265 | xxxxxxxx | Sleep | 6 | | NULL | 0.000 |
| 61 | xxx | xxx.xx.xx.xx:50266 | xxxxxxxx | Sleep | 6 | | NULL | 0.000 |
| 62 | xxx | xxx.xx.xx.xx:50267 | xxxxxxxx | Sleep | 6 | | NULL | 0.000 |
| 63 | xxx | xxx.xx.xx.xx:50268 | xxxxxxxx | Sleep | 6 | | NULL | 0.000 |
| 64 | xxx | xxx.xx.xx.xx:50269 | xxxxxxxx | Sleep | 6 | | NULL | 0.000 |
| 65 | xxx | xxx.xx.xx.xx:50270 | xxxxxxxx | Sleep | 6 | | NULL | 0.000 |
| 66 | xxx | xxx.xx.xx.xx:50271 | xxxxxxxx | Sleep | 6 | | NULL | 0.000 |
| 67 | xxx | xxx.xx.xx.xx:50272 | xxxxxxxx | Sleep | 6 | | NULL | 0.000 |
| 68 | xxx | xxx.xx.xx.xx:50273 | xxxxxxxx | Sleep | 6 | | NULL | 0.000 |
+----+------+--------------------+----------+---------+------+-------+------------------+----------+

계속 보고있지 뭐.
60초가 지나니까 다 죽어 없어짐.
wait_timeout 을 10으로 조정. 역시 10초만에 다 죽음.

my.cnf 에 있는 설정값이 제대로 나오지 않는 것으로.
야…

네이밍에 대해서

헝가리안 표기법?

이제는 이런 표기를 안 쓸거라고 생각하지만 헝가리안 표기법이라는게 있다. 변수앞에 타입을 접두어로 붙이자는 것으로 주로 마소에서 개발해서 배포하는 vc를 쓰는 프로젝트에서 이런 표기법이 주로 보였다.

본격MSDN예제link
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include "sy.h"
extern int *rgwDic;
extern int bsyMac;
struct SY *PsySz(char sz[])
{
char *pch;
int cch;
struct SY *psy, *PsyCreate();
int *pbsy;
int cwSz;
unsigned wHash=0;
pch=sz;
while (*pch!=0
wHash=(wHash<>11+*pch++;
cch=pch-sz;
pbsy=&rgbsyHash[(wHash&077777)%cwHash];
for (; *pbsy!=0; pbsy = &psy->bsyNext)
{
char *szSy;
szSy= (psy=(struct SY*)&rgwDic[*pbsy])->sz;
pch=sz;
while (*pch==*szSy++)
{
if (*pch++==0)
return (psy);
}
}
cwSz=0;
if (cch>=2)
cwSz=(cch-2/sizeof(int)+1;
*pbsy=(int *)(psy=PsyCreate(cwSY+cwSz))-rgwDic;
Zero((int *)psy,cwSY);
bltbyte(sz, psy->sz, cch+1);
return(psy);
}

이런ㅆㅂ뭐라는거야

문제점

예제에서 나오는 변수명은 사실 상당히 짧다. 본래의미를 축약하지 않고 다 써도 상당히 짧은 편일 것이다. 헌데 그 의미마저도 괴상한 접두어 때문에 가려져서 잘 보이지 않는다. 실제 업무용 어플리케이션은 이보다 훨씬 길고 다양한 의미를 가진 변수명을 쓰게 되는데 이럴때 이 의미가 가려지는 현상 은 훨씬 심해진다. 과연 저 괴상한 접두어들이 필요한가? 예를들어 불린타입의 숫자인지 아닌지에 대한 값이 들어있는 변수를 헝가리안 표기로 나타내면 bNumber 이다. 왜 안 isNumber ? 또 스킴이나 루비같은 언어에서는 가장 좋은 컨벤션을 쓴다( number? ). 게다가 축약된 접두어를 붙이느라 길어진 변수명을 짧게/접두어와 어울리게 만드느라 변수명을 줄여쓰는 사람들이 많은데 그러자면 bNo 가 된다. 여기에 포인터를 의미한다는 p를 붙여 pbNo. 값을 얻는답시고 *pbNo.

물론 이같은 것들은 이제 없으나 요런 사상은 아직도 여기저기에 남아있다. 주로 데이터베이스에 말이지.

Jade?

hexo테마

hexo로 블로그를 만들면서 테마때문에 꽤 골치를썩음.
일단 요 테마 strict 는 jade 라는 nodejs템플릿 라이브러리를 쓰고있음
그런데 보면 jade는 일정 형식을 가진 소스코드 -> 컴파일 -> html 의 구조를 갖는 템플릿 엔진이었다.

몇번 굴려보고 느낀점은..
야 이럴거면 그냥 clojure 하지;..

어쩌다보니 그냥 clojure 얘기

클로져 (혹은 다른 lisp, scheme 류) 에서는 이미 저런 시도가 흔하다.

hiccup.core.htmllink
1
2
3
4
5
6
7
8
9
10
11
12
;; user=>
(html [:div#foo.bar.baz "bang"])
;=> "<div id=\"foo\" class=\"bar baz\">bang</div>"
;; user=>
(html [:ul
(for [x (range 1 4)]
[:li x])])
;=> "<ul><li>1</li><li>2</li><li>3</li></ul>"

클로져의 웹프레임워크 라이브러리중 하나인 lib-noir 를 쓰면 defpage 매크로를 써서 url에 즉시 바인딩 시킬 수 있다.

1
2
3
4
5
(defpage "/test" []
(hic/html [:html
[:head]
[:body
[:h1 "Hello"]]]))

지금도 잘 쓰고있는 자바 라이브러리로 stripe 라는게 있다.
앞서 얘기한 재료를 잘 반죽하여 stripe를 구현할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(defpartial layout [ & {:keys [script
header
content
footer]}]
(html5
[:head
[:title "my-website"]
(include-css "/css/reset.css")
(include-js "/js/main.js")
`(~@script)]
[:body
[:div#header `(~@header)]
[:div#wrapper `(~@content)]
[:div#tail `(~@footer)]]))
(defpage "/foo/page/select" []
(layout
:script
[[:script "my_website.core.alert_hello('hello');"]]
:header
[[:h1 "Hello"]]
:content
[[:table ]]))

결론

물론 php, jsp 등 흔히 server page 라고 부르는 방식으로 만들수고 있고, html을 파싱해다가 원하는 부분만 코드가 개입하도록 할 수도 있다.
요건 개취인듯.
하지만 협업을 포기한다면 (다른데는 모르겠고, 우리나라에서는 반드시 포기해야한다)
clojure의 저 접근이 괜찮은듯.
jade는 몰라.

contrib

깃헙에 공개한 오픈소스 모음

test-dies

  • https://github.com/zerosumz/test-dies
  • markdown extra 의 테이블 문법으로 작성된 테이블을 데이터베이스의 테이블로
    • 매 테스트메서드 호출시마다 인서트
    • 매 호출이 끝난후 삭제

OO에 대한 느낌적 느낌

자바

나는 주로 먹고살기위해 자바를 써왔다. 현재도 많은 사람들이 바로 이 이유로 자바를 선택한다.
OO는 멋지다. 솔직히 간지폭발이다. 하지만 자바를 위시한 현실에서는 별로 안 멋지고 안쓰느니만 못한 부분도 간간히 보인다.

간단한 아이디어의 구현

예를들어 이런 수식이 있다고 하자. 흔히 예제로 자주드는 시그마 표현식이다.

$\sum\limits_{n=a}^b f(x) = f(a) + f(a+1) + … + f(b-1) + f(b)$

이것을 다른 평범한 언어 - 예를들어 스킴이나 자바스크립트, 좀더 내려가서 C에서는 어떻게 구현할까?

스킴
1
2
3
4
5
6
7
8
(define (sigma f a b)
(if (> a b)
0
(+ (f a)
(sigma f (add1 a) b))))
(sigma (λ(x) (* x x)) 1 2)
; => 5
자바스크립트
1
2
3
4
5
6
7
8
9
10
function sigma(f,a,b){
if(a > b)
return 0;
else
return f(a)
+ sigma(f, a+1, b);
}
sigma(function(x){return x*x}, 1, 2);
// => 5
C
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
int sigma(void*, int, int);
int term(int);
int main(int argc, char *argv[])
{
printf("%d" ,sigma(&term, 1, 2));
// => 5
return 0;
}
int sigma(void* f, int a, int b){
if(a > b)
return 0;
else
return ((int(*)(int))f)(a) + sigma(f, a + 1, b);
}
int term(int i){
return i * i;
}

하지만 자바에서는 OO의 이름으로 널 용서하지 않을것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package example;
public class Main {
public static void main(String[] args) {
System.out.println(sigma(new Term() {
@Override
public int term(int x) {
return x * x;
}
}, 1, 2));
}
// => 5
public static interface Term {
public int term(int x);
}
public static int sigma(Term f, int a, int b) {
if (a > b)
return 0;
else
return f.term(a) + sigma(f, a + 1, b);
}
}

하지만 자바를 오래해서 그런지, (또한 돈을 벌어다줘서 그런지) 저런 표현도 나쁘지 않다는 생각이 든다.
아직까진 참을 수 있다. 오히려 저렇게 뭔가 명시적이고 구조적이고 엔터프라이지-하고 꾸띄르적이고 뭔가 저렇게 하면 세련되고 아트적인 느낌을 줄 것만 같다.

복잡한 아이디어의 구현

현실세계로 돌아와서 자바로 허구헌날 만드는 웹 어플리케이션이나, 웹어플리케이션 혹은 웹 어플리케이션을 보자. 유행의 첨단을 주도하는 셀러브리티 트랜드 세터들이 매일 뭔가 프로퍼 웨이- 한 패턴이나 스타일을 들고 나온다. 그 중 하나가 aspect 다. respect
자바는 이 aspect oriented programming 을 위해서 굉장히 여러 시도를 하였으며 관련 프로젝트도 여럿 나오게 된다. 현실에서 우리가 자주 보는 spring, spring-aop 가 바로 현실적인 표준으로 자리잡은 프로젝트라 할 만하다. 전자정부에도 있다 이거. 그런데,

  • DI, aop 를 써서 무언가를 만드는것은 간지나고 좋다.
  • 그런데 위에서 간단한 아이디어를 구현하는데에 이러한 복잡한 기능들이 방해가 되지는 않을까?
  • 그러니까, 위에서 했던 간단히 익명 클래스 인스턴스를 넘겨받고 내부의 메서드를 호출하는등의 기능이 aop를 쓰면서도 잘 굴러갈까?

경험상, 위의 예시들은 될 수도 있고, 안될 수 도 있다. 보통 jdk proxy를 쓰느냐, cglib proxy를 쓰느냐에 따라서 되고 안되는 것들이 생겨난다. 게다가 간단히 익명 클래스 인스턴스를 넘겨받는 일들을 스프링 웨이- 하게 하려면 (불과 몇년 전까지만 해도) xml 파일에다가 해당 빈의 스펙을 기술해야만 했다.

현실에서 가장 만나기 좋은 예시는 @Transactional 이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public abstract class PaymentService<REQ extends PayRequestable, RESP extends PayResponseable, PAY extends Payment> {
@Transactional
public abstract PAY draftPayment(long paymentId, REQ payRequest, Order order) throws SystemException;
@Transactional
public abstract RESP requestPayment(REQ payRequest, Order order) throws SystemException;
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public abstract void completePayment(PAY payment, RESP payResponse) throws SystemException;
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public abstract void logPayment(User user, PAY payment, RESP response);
@Transactional
public PAY process(Order order, long paymentId, REQ payRequest) throws SystemException {
// .... 결제처리를 한다.
return payment;
}
@Transactional(propagation = Propagation.NEVER)
public void checkExistingOrder(User user) {
// .....
}
@Transactional(propagation = Propagation.NESTED)
public void saveOrderItemsAppliedPromotion(Order order) {
// .....
}
}

위의 시그마 표현식과 다를바 없는 간단한 아이디어지만 이 코드들이 제대로 동작하게 하기위해서 어노테이션의 특성을 또 파악하고 스프링의 설정상 함정을 회피하고 또한 하나의 스타일을 강요해야한다.

  • 별로 멋있지도 않고
  • 불안하고 무섭고
  • 힘들다

결론은 많은 사람들이 간단한 아이디어를 포기하고 괴랄한 구현을 선호하게 된다. 서비스에서 내부 메서드를 호출하지 못하므로 그냥 서비스 인터페이스를 하나 더 만들고 서비스 구현체 클래스를 하나 더 만드세요 ~ 물론 다른 방법도 있지만 그건 표준적이지 못하고 팀워-크에 민폐를 끼치는 일이에요~ 무엇보다 무슨일이 생길지 몰라요~ 라고 하는 일들이 벌어진단 얘기.

수백줄에 이르는 xml이 왜 아직 남아있겠는가요?

결론

스킴이나 이쪽 동네에는 좀 다른 클래스와 제네릭 메서드를 구현하는 방법이 있긴 하지만…

  • 아직 늦지 않았다. 공무원을 하는게 좋다. 당장 공무원 공부를 시작하면 좋다.
  • 왜 이런 자바쟁이 짓을 하고 있는지 모르겠다. 이런거 한다고 누가 알아주지도 않는다.
  • 결론따위 없다. 내가 뭐 아는게 있어야지. 감히 신성한 패턴에 대해 논하다니 잠깐 미쳤었나 보다.