Clean Code that Works.

Neo4j를 회사 서버에 설치해서 사용중인데 집에서도 공부할때 계속 사용해보고 싶었다.

Grails에서 기본적으로 제공해주는 environments 는 development, product, test 이렇게 지원을 해주는데, 나는 집에서 쓰고 싶으니까 별도로 값(home)을 지정해서 사용하고 싶었다. 


Grails 에서 제공하는 Environments 는 여기(http://www.grails.org/Environments)에 보면 자세히 설명이 되어 있고, 나는 home으로 사용하고 싶으니까 서버를 실행시에 아래와 같이 입력하면 된다.

grails -Dgrails.env=home run-app

그럼 이 환경을 사용하는 변수들을 설정해 주어야 하는데..

아래와 같이 설정하면된다.

environments {

home {

dataSource {

dbCreate = "create-drop"

url = "jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000"

}

grails {

neo4j {

type = "embedded"

  location = "/var/neo4j"

}

}

}

development {

dataSource {

dbCreate = "create-drop" // one of 'create', 'create-drop', 'update', 'validate', ''

url = "jdbc:h2:mem:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000"

}  

grails {

neo4j {

type = "rest"

  location = "http://100.0.0.1:7474/db/data/"

}

}

}

}


기본으로 run-app를 하면 development 로 올라가고, 위에 처럼 별도로 home으로 설정해주면 별도로 설정한 값을 가지고 실행된다. 

neo4j가 embedded로 설정되면 설치된 neo4j 플러그인이 자동으로 neo4j를 실행시켜 주므로 따로 설치할 필요는 없다(h2 db 처럼)

neo4j 설정은 아래를 참조.

http://springsource.github.com/grails-data-mapping/neo4j/manual/guide/gettingStarted.html#usingNeo4jStandalone



어제 집에서 이클립스 설치하고 grails 한번 해볼려고 했는데, git 에서 받은 소스가 빌드가 안되는 문제가 발생했다.

회사에서 커밋이 다 안됬나.. 하고 살펴봤는데..

grails.org 들어가보니 central plugin repository가 어제 하루 다운되서 그랬었다는...

다행이 오늘 빌드 해보니까 다 되는구나. ㅋ


하지만.. 띄워 볼수는 없을듯.......... neo4j는 사내 서버에 설치되어 있어서.. 사내에서만 접속된다.

oops .. 


neo4j 설정은 profile 지정이 안되나..


요즘 보는 것..

grails2012. 11. 15. 18:50

요즘 nosql 중 하나인 neo4j를 잠깐 보고 있는데..
셀프 참조하는 객체를 가져와서 도메인 클래스로 매핑해주는 방법을 못찾겠다.

cypher 쿼리로 작성해서 결과를 가지고 오면 RestNode 형식으로 값을 가져 오는데, 이 값이 내가 원하는 domain으로 자동 매핑 되면 좋겠지만.. 안된다. 

아 물론 그전에 최상위 객체를 가져오면 자동으로 셀프 참조하는 객체들 정보도 다 가지고 오면 좋겠다. 하이버네이드에서는 .. 무슨 옵션이더라.. max_fetch_depth 이건가 이걸로 연관된 객체들 값을 가져오는 dept를 지정할수 있었던것 같은데...

어렵구만...

https://github.com/KimJejun/grails-first/blob/master/grails-basic/grails-app/controllers/grails/basic/StartPointController.groovy

여기 소스 보면, show 하는 쪽에서 cypher 쿼리 사용해서 결과 매핑을.. 해야 하는데.. 음 어떻게 좀 자동으로 안되나..



위는 grails 공부하면서 보는 곳.


공부를 사내에서만 해서 -_-;; 

회사에서 지원해주는 내부 클라우드서버 에다가 neo4j 설치해서 사용하는 중이다.



보통 JSON을 사용이 필요할때는 화면에 결과로 만들어줄 때나, rest 한 요청등이 들어 왔을때 결과로 찍어 주기 위해서 사용하는데.. Grails에선 손쉽게 사용할 수 있다.

http://grails.org/doc/latest/guide/theWebLayer.html#xmlAndJSON

위 API 설명..

(groovy에서 제공하는 JsonBuilder를 사용해도 되나.. Grails에서 제공하는 방법이 더 쉽다.)


그냥 간단하게.. json 변환이 필요한 오브젝트에 .encodeAsJSON() 해주면 된다.

같은 방식으로 xml 등등 다양한 방식을 지원.


만약 결과로 json을 보내줘야 한다면 컨트롤러에서 render obj.encodeAsJSON() 하면 되고, 리스트 객체도 같은 방식으로 listObj.encodeAsJSON() 하면 자동으로 json 형식으로 만들어서 뿌려준다.

gsp 뷰에서 json으로 변환하고 싶으면 ${obj.encodeAsJSON()} 으로 사용하면 되고,

만약 obj가 null 이면 null 인 json 객체가 생성된다.

GSP 에서 데이터 표시해줄때

<g:hiddenField name="id" value="${startPointInstance?.id}" />

이렇게 쓰는데.

startPointInstance? ... 의 ?가 의미하는게 궁금해서 검색해봤다.

"Safe Navigation Operator" 란다. 만약 startPointInstance 인스턴스가 null 이면 id 에 접근하려고 하지 않는다.

(jsp 에서는 없는 프로퍼티나 값이 없으면 아무것도 표시 안하거나.. null로 나왔던것 같음)

http://stackoverflow.com/questions/4581532/what-does-the-question-mark-mean-in-gsp-grails

여기 가보면 자세한 설명이 있다. null 일경우 디폴트 값 지정하는 방법도.

좋구만 ~_~

http://lmgtfy.com/?q=gsp+question+mark




rest 방식으로 neo4j 사용할 때, 

grails 설정(DataSource.groovy)에 rest 방식으로 지정을 해 주고, 여기에 필요한 의존성을 추가 해 줘야 하는데.


이 파일이 어디 있냐면은..

http://m2.neo4j.org 요기, neo4j에서 제공하는 메이븐 레파지토리에 있다.


그러므로 BuildConfig.groovy에 아래와 같이 mavenRepository 추가하고

mavenRepo 'http://m2.neo4j.org/'

빌드 해서 연결 하도록 하자


보통 Spring에서 스케쥴링 기능을 사용할 때, @Scheduled 애노테이션을 사용한다. 

쉽고 간편하다. <task:annotation-driven /> 설정을 해주거나 3.1 부터 추가된 @EnableScheduling을 설정 클래스에 추가해주면 된다.


그럼 이 annotation은 spring이 어떻게 감지 해서 동작하는 것일까?

ScheduledAnnotationBeanPostProcessor를 살펴보면 답이 나와있다.

이 Bean post-processor가 @Scheduled 애노테이션이 붙어있는 메서드들을 다 찾아서 TaskScheduler에다가 다 등록을 해준다.

주석엔 아래와 같이 설명이..

Bean post-processor that registers methods annotated with @{@link Scheduled}

to be invoked by a {@link org.springframework.scheduling.TaskScheduler} according

to the "fixedRate", "fixedDelay", or "cron" expression provided via the annotation.


사실 갑자기 왜 이걸 보게 되었냐면.....

@Scheduled 옵션을 사용할때 고려되는 문제점이 cron 옵션으로 동작하게 할 수 있는데, 이때 서로 다른 서버에서 같은 동작을 하는 작업을 할 때 cron 식을 달리 하고 싶은 경우가 많다. spring batch 호출 메서드를 각 서버마다 다른 시간으로 호출 하고 싶은경우...

해서, ScheduledAnnotationBeanPostProcessor 살펴보니 여기서 @Scheduled 애노테이션의 cron 식 설정을 불러다가 CronTask를 만드는데 사용을 하고 있는걸 확인할 수 있었다. cron에 cron 식이 들어 오면 그대로 쓰고 설정값('${batch.run.cron}') 형식이 들어오면 프로퍼티 파일에서 읽어다가 쓰는 방식.


그럼 @Scheduled 를 상속 하거나 약간 수정해서 프로퍼티 파일에서 값을 읽는 부분을 수정(동작할 때 서버의 host 명을 가지고 와서 이 host 명 + .batch.run.cron ' 식으로 cron 값을 서버별로 읽어 들이는) 하는 방식으로 말이다.

이렇게 동작 시키기 위해서 ScheduledAnnotationBeanPostProcessor 를 참조 하여 별도의 BeanPostProcessor(별도라고는는 하지만, 설정파일 읽는 부분만 변경 하면된다)를 만들고 이를 bean으로 등록 해주면 간단히 구현이 가능해진다.


ScheduledAnnotationBeanPostProcessor 를 3.0 버전 3.1 버전 3.2 버전 별로 살펴보면 조금씩 리팩토링을 해 나간것을 볼 수 있다. (이런거 살펴보는것도 나름 소소한 재미 인듯, 다른 사람 소스도 살펴보고)  바뀐 부분은 ScheduledTaskRegistrar 에다가 cronTasks, fixedDelayTasks, fixedRateTasks 세팅하는 부분이 좀더 캡슐화 되어 보기 더 편해졌다는 정도?

before

if (!"".equals(cron)) {

processedSchedule = true;

if (embeddedValueResolver != null) {

cron = embeddedValueResolver.resolveStringValue(cron);

}

cronTasks.put(runnable, cron);

}


after

if (!"".equals(cron)) {

processedSchedule = true;

if (embeddedValueResolver != null) {

cron = embeddedValueResolver.resolveStringValue(cron);

}

registrar.addCronTask(new CronTask(runnable, cron));

}




http://ebs.daum.net/knowledge/episode/7513?t__nil_issue=uptxt&nil_id=4

하루에 생산되는 데이터. 250경 바이트

지난 2년간 생산된 데이터량  > 인류가 그동안 쌓아온 데이터.

권련이 빅 데이터를 악용할 경우, 데이터를 활용해 통제할 수 있는 빅 브라더가 탄생할 수 있다.


간만에 spring security 로 ip 필터링을 할 부분이 있어서 적용중에...


문서를 보면 hasIpAddress(10.54.100.0/24)  이런 부분이 있다.

음..딱 보면 10.54.100.0~24 까지 ip 필터링을 하는가 보다... 라고 생각하고 그렇게 했는데. 

이 뜻이 아니다. -ㅁ-;;


http://forum.springsource.org/showthread.php?102783-How-to-use-hasIpAddress

위 블로그에 똑같은 질문을 했는데, 친절하게 답변을 해줘서 이해가 쉬웠다.


만약 10.54.100.0 ~255 까지 ip를 블럭 하고 싶으면, 

10.54.100.0/24 이렇게 쓰면된다. 그런데 뒤에 /24는 무엇이란 말인가.

24가 의미하는것은 ip 주소의 subnet mask의 바이너리 값을 더 한것이다.


일반적으로 윈도우나 리눅스에서 ipconfig를 쳐보면 sutnet mask(mask)를 확인할 수 있다.

255.255.255.0

255의 바이너리 값이 11111111 이 되므로

(11111111) * 3 = 24, 이 24가 위 표현에서 /24가 되는것이다.


헐~ 역시 스프링 시큐리티 문서는 아직도 불친절해 -ㄴ-

http://blog.springsource.org/2012/08/29/integrating-spring-mvc-with-jquery-for-validation-rules/

위에 있는 내용 간단히 번역 및 소스 만들기


컨트롤러들에서 리퀘스트 맵핑, 어노테이션을 통한 리퀘스트 데이터 추출, 데이터 바인딩, 파일업로드 등등  전형적인 Spring MVC 의 특징들을 볼 수 있다.

반면에 JSP 안에서는 대부분의 HTML들이 기본적으로 존재 하고 있다.(Spring MVC 태그에 의해 생성되기를 반대하는) 더욱이 Spring MVC 태그 라이브러리들은 어떤 자바스크립트 코드도 생성 하지 않는다.

처음으로 상의 해야 할 내용은 어떻게 Spring MVC와 Jquery 그리고 Bean Validatin을 통합할 것인가 이다. 어떻게 JSP를 줄일 수 있는지 살펴보자

BEAN VALIDATION?

JSR 3030 Bean Validataion은  포괄적인 방법으로 선언적 Validation 방법을 제공한다. 

아래예제를 살펴 보자.

public class DiningForm {
  @Pattern(regexp="\\d{16}")
 
  private String creditCardNumber;
 
  @Size(1)
  private String merchantNumber;
 
  @Min(0)
  private double monetaryAmount;
 
  @NotNull
  private Date date;
 
  ...
 
}

유효성 검증이 호츨 되면 DiningForm의 인스턴스는 위의 Annontations에 의해 유효성이 검증 된다.

Spring 3.0에서, Spring MVC는 유효성 검증을 위한 Bean Validation을 통합했다.(위 방식의 @MVC의 유일한 방법은 아니나 명백하게 가장 대중적인 접근 방법이다.)

컨트롤러 메소드에서 아래와 같이 @Valid를 사용할 수 있다.

@RequestMapping(value="/update", method=RequestMethod.POST)
  public String update(@Valid DiningForm diningForm, BindingResult result) {
 
    if (result.hasErrors()) {
      return “rewards/edit”;
    }
 
    // continue on success...
 
  }
}

JSP 화면에서 에러메세지는 <form:errors /> 를 사용해서 표시될 수 있다.

<form:form modelAttribute="diningForm">
  <form:input path="firstName"/>
  <form:errors path="firstName"/>
  
</form:form>

위의 코드는 잘 작동하고 매우 심플하다. 하지만 어떠한 자바스크립트 코드도 생성하지 않는다. 따라서 부분적인 렌더링이나 클라이언트 측 유효성검사를 하지 않는다. 이 방법을 어떻게 행상시키는지 보도록 하자!


부분 렌더링을 위한 자바스크립트 추가.


"first name" 부분이 비어 있을때 무슨일이 발생하는지 살펴보자.

이전 예제에서 form 이 전송될 때 마다 모든 페이지가 전부 새로고침된다.

응답받은 HTML 소스는 아래와 같다.

우리의 목표는 응답 사이즈를 최소화 하는 것이다. JSON 방식을 사용해서 할 수 있다.

{"status":"FAIL","result":[{"fieldName":"firstName","message":"firstName  may not be empty"}]}

먼저 from 유효성 검증을 위해 jQuery form 전송을 사용할 것이다. form 이 유효성 검증을 통과할 때 form 일반적인 HTML form 전송 방식을 통해 전송된다.(이런식으로 다른 페이지로 redirect될 수 있다.)

간단한 ValidatationResponse 클래스를 만들어 보자

public class ValidationResponse {
 private String status;
 private List errorMessageList;
 
 public String getStatus() {
   return status;
 }
 public void setStatus(String status) {
   this.status = status;
 }
 public List getErrorMessageList() {
   return this.errorMessageList;
 }
 public void setErrorMessageList(List errorMessageList) {
   this.errorMessageList = errorMessageList;
 }
}

controller 클래스에서 action method를 추가하자.

@RequestMapping(value="/user.json")
public @ResponseBody ValidationResponse processForm (Model model, @Valid User user, BindingResult result ) {
 ValidationResponse res = new ValidationResponse();
 if(!result.hasErrors()){
   res.setStatus("SUCCESS");
 }
 // …
 return res;
}

@ResponseBody annotation에게 감사를 돌린다. 응답하는 오브젝트는 아래 다이어그램에서 설명하는 것처럼 JSON  으로 변경된다.

JSP에서 에러 메시지가 도착했을 경우 이것을 파싱해서 화면에 표시 해야 한다. 이 링크에서 좀 더 자세한 자바스크립트 소스를 볼 수 있다.

진보적인 향상 모범사례에 따르면 모든 자바스크립트 코드는 HTML form 바깥에 위치 해야 한다. 클라이언트 브라우저에서 자바스크립트가 사용불가로 되었을때 form은 모든 페이지 리프레시가 되도록 지원 해야 한다.

지금 부분적인 갱신작업을 위한 유요성 검증 작업을 진행하고 있는데, 2가지 개선 포인트가 있다.

  • 페이지 화면이 구리다.
  • 이 hello-world-style 페이지는 이미 100라인이나 된다. 이것을 줄일 방법이 필요하다.


BOOTSTRAP을 사용하여 포장 하기


이것은 Spring MVC와 연관되어 있지는 않지만 이런 허접한 UI 디자인가지곤 샘플 애플리케이션이라 하기가 어려웠다. 아직 들어 본적이 없는 경우에 트위터 Bootstrap 은  CSS 프레임워크처럼 되고 있다. 많은 개발자들이 이것을 좋아하는데 그 이유는 작은 노력으로 만족할만한 웹사이트를 만들 수 잇기 때문이다. Bootstrap CSS와 이미지들을 복사하고 난 후에 사용하면 된다(상세 코드는 이 링크). form 이 아래와 같은 모습으로 변경되었다.


"JSP SOUP"를 방지하기 위한 커스텀 태그 사용.

여기가 정말 재미있어지는 부분이다. 이미 작동하는 몇가지 코드가 있기는 하지만 이것을 읽기가 좀 어렵다. HTML, Javascript, CSS 와 JSP EL을 서로 섞을 것이다. 아래 코드처럼 좀 더 읽기가 편한 JSP 코드가 될 것이다. 

<html:form modelAttribute="user"  id="add-user-form" formUrl="/userAjaxCustomTag.htm">
 <html:inputField name="firstName" label="Enter your first name:" />
 <html:inputField name="lastName" label="Enter your last name:" />
 <div>
   <button type="submit">Save changes</button>
   <button type="reset">Cancel</button>
 </div>
</html:form>

커스텀 태그는 JAVA EE 의 한 부분이고 아파치 톰캣에서 완벽하게 동작한다. 놀랍게도 커스텀 태그를 만드는 일은 매우 쉽다. form input 필드를 사용하는 간단한 예를 보자. 아래 8라인의 코드를 보자.

<div id="firstName">
 <label>Enter your first name:</label>
 <div>
   <form:input path="firstName" />
   <span>
     <form:errors path="firstName" />
   </span>
 </div>
</div>

우리 목표는 이것을 1라인으로 표시하는 것이다.

<html:inputField name="firstName" label="Enter your first name:" />

WEB-INF 폴더에다가 아래처럼 새로운 태그 파일을 작성한다.

이 내용은 아래와 같다.

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ attribute name="name" required="true" rtexprvalue="true" %>
<%@ attribute name="label" required="true" rtexprvalue="true" %>
<div id="${name}">
 <label>${label}</label>
<div>
  <form:input path="${name}"/>
  <span><form:errors path="${name}"/></span>
</div>

userForm.jsp 파일로 돌아와서 커스텀 태그를 사용하기 위해 tag 폴더를 선언해준다.

<%@ taglib prefix="html" tagdir="/WEB-INF/tags/html" %>

아래처럼 새롭게 생성된 태그가 사용 가능 하다.

<html:inputField name="firstName" label="Enter your first name:" />

커스텀 태그는 이클립스나 STS 에 잘 통합되어 있어 코드 자동완성으로 접근이 가능하다.

같은 모양으로 자바스크립트 코드를 태그로 끄집어 내서 아래처럼 한줄로 호출 할 수 있다.

<ajax:formPartialRefresh validateUrl="/userAjaxBootstrap.json" formName="add-user-form"/>

결론

Spring MVC와 폼 유효성 검증에 대한 부분적인 렌더링에 대해서 알아 봤다. 겨우 몇분만에 JSP soup 을 좀더 간단하고 이해하기 쉬운 방법으로 변경 하였다. 

샘플 코드는 이 github에서 확인할 수 있다.