'Framework/Validation'에 해당되는 글 2건

  1. 2009.02.18 Framework - Validation(Java)
  2. 2009.02.17 Framework - Validation
Framework/Validation2009. 2. 18. 02:08

Framework를 작성할때 가장 먼저 생각해야 하는것은 실험실과 비실험실의 분류이다. 실험실이란 일종의 무균의 연구실을 상상하면 된다. 이 곳에서는 이 코드가 실제 사용될 비실험실의 제약에 대해 전혀 고려하지 않는다는게 포인트다. 오직 코드의 효율 그 자체만을 추구하며 공학이 가지는 모든 특성을 무시해도 상관없고 오히려 의도적으로 무시되어야 한다.

실험실의 코드는 오직 코드의 1차 목적에만 집중하는데 일차적으로 코드의 효율성이나 디자인의 간결함만을 추구해야 한다. 실험실의 코드는 비실험실에 대해 의도적으로 무시하기 때문에 비 실험실은 실험실 코드에 대해 최소한의 정보만 알기 때문에 request와 response 모두 인터페이스 기반 통신을 하게된다.

말은 조금 어려워졌지만 사실 유효성 검사와 관련한 실험실 코드를 작성하는 것은 그닥 어려운 일이 아니다.

첫째 고려해야 할 것은 유효성검사가 필요한 객체타입은 - 아마도 대부분 Getter 함수를 가지고 있는 Bean 형태의 객체가 되겠지만 - 비실험실에서 어떻게 사용될지 모르는 실험실 코드에서는 제약할수 없기 때문에 Object 형태가 되어야 한다. 다만 그냥 Object는 좀 그러니까 Serializable 인터페이스같이 메소드 정의가 없는 IValueObject 인터페이스를 사용하기로 하자.

public interface IValueObject {

}

public class TestBean implements IValueObject {

 private int ivalue;
 private String svalue;

 public int getIvalue() {
  return ivalue;
 }

 public void setIvalue(int ivalue) {
  this.ivalue = ivalue;
 }

 public String getSvalue() {
  return svalue;
 }

 public void setSvalue(String svalue) {
  this.svalue = svalue;
 }

}
와 같은 TestBean을 만든다. 비실험실에서 실험실 코드로 IValueObject 를 던지면 실험실 코드의 response도 하나의 interface가 될수도 있겠지만 사실 boolean isValid() 말고는 그닥 특별한 정보가 필요가 없다. 말했다시피 실험실은 외부와 차단되어 있어야 하기 때문에 최소한의 정보만을 주고받는다.

따라서 실험의 모든 유효성 검사 객체는 아래 인터페이스를 구현하게 된다.




public interface IValidator {
 public boolean isValid() ;
}

예를 들어 공백이어서는 안된다라는 유효성은
public class Required extends BaseValidator{

 public Required(IValueObject valueObject, String name) {
  super(valueObject, name);
 }

 public boolean isValid() {
  Object value = invokeGetter() ;  // reflection
  if (value == null) return false ;
  
  return StringUtil.isNotBlank(value.toString()) ;
 }
}

와 같이 작성될 것이다.

테스트 코드는 아래와 같이 작성된다.

public class StandardValidationTest extends TestCase{

 private TestBean b = new TestBean() ;
 private String SVALUE = "svalue";
 
 public void setUp(){
  b = new TestBean() ;
 }

 public void testRequired() throws Exception {

  assertEquals(false, new Required(b, SVALUE).isValid());
  b.setSvalue("") ;
  assertEquals(false, new Required(b, SVALUE).isValid());

  b.setSvalue(" \t\n") ;
  assertEquals(false, new Required(b, SVALUE).isValid());

  b.setSvalue("a") ;
  assertEquals(true, new Required(b, SVALUE).isValid());
 }
}

이런식으로 실험실 코드를 작성하면 된다. 사실 실험실 코드를 작성하는 것은 아주 쉬운일이다. 제약과 예외가 거의 없기 때문이다. 그리고 물론 제약과 예외를 고려하지 않는 것은 의도적인 것이다. 제약과 예외는 비실험실에서 생기는 것이며 그곳의 문제를 결코 실험실안으로 끌고 들어와서는 안된다.


이제 온갖 세균과 예외와 제한이 있는 비실험실인 현실세계로 돌아와 보자.
앞서 말했듯이 실제 어려운 것은 현실세계의 접촉 지점이다. 

1. 프로그램에서 직접 호출하는 방법
   모든 프레임워크가 그런것은 아니지만 유효성검사 프레임워크는 프로그램에서 직접 호출방식은 거의 고려할 필요가 없다. 왜냐하면 유효성 검사라는 말 자체가 가지는 개별적인 비지니스 특징때문이다. 어떤 객체의 유효성이란 거의 항상 비지니스 도메인에서 개별적으로 정해지기 마련이고 이는 다시 말해서 굉장히 유연해야 하므로 컴파일이 되는 딱딱한 바이트 코드에 넣어서 관리하는 것은 의미가 없다.

2. XML 설정 파일을 이용하는 방법 
   사실 대부분의 구성 정보는 XML을 통해 관리하는게 정답이긴 하다. 이전의 Property 파일보다는 다 짜임새 있기 때문이다. 다만 XML 파일은 앞서 말한대로 효과에 비해 지나치게 복잡하다. 정도의 문제이긴 하지만 일반 구성 정보가 아닌 수십 - 수백개에 이르는 개체의 대한 유효성 정보를 XML파일로 관리하는 것은 쉬운 일이 아니다. 게다가 유효성 검사의 특성상 많은 중복 정보가 발생하는데 - 이를테면 Required 가 필요한 수는 객체수 * 프로퍼티 * 0.3 정도이다. - 이를 효과적으로 처리하는게 어렵다.

두번째로 접착성이 떨어지는데 여기서 접착성이란 비지니스 프레임워크와 너무 단단하게 연결되어 있기 때문에 프레임워크의 이전이 쉽지 않다는 문제도 있다. 사실 현재의 Struts와 Spring의 Validation Code의 가장 큰 문제가 이거다. 더군다나 편의를 위한다고 써먹지도 못하는 듣보잡 자바스크립트 코드를 지멋대로 생성하곤 한다. - 이딴걸 어따 쓰라고 -ㅅ-; 프레임워크는 어디에도 붙일수 있는 포스트 잇의 접착도를 가져야 하지 일단 붙으면 떨어지기 힘든 본드의 접착도를 가져서는 안된다.

XML파일로 관리했을때 세번째 문제는 복잡한 유효성 감사를 다루기가 어렵다는 데 있다. 물론 그에 해당하는 CustomBean을 만들어서 어떻게 해결할 수도 있겠지만 케이스 바이 케이스로 하나씩 만든다는 것은 지나친 수고로움을 유발한다.


그럼 어떻게 해야할까? 보통 다른 선택이 있다면 Rule Engine를 제작하는 방법이 있다. Rule 엔진은 간단한 정의와 프로세싱 기능이 있기 때문에 XML파일보다는 훨씬 유연하고 자유롭다. 다만 일반적으로 Rule Engine은 보편성을 갖기가 힘들기 때문에 유효성 검사의 경우에 한정한다면 배보다 배꼽이 커지는 경우라 할 수 있다. 

그래서 가장 그럴듯한 다른 방법은 자바에 내장되어 있는 Rhino 같은 스크립트 엔진을 활용하는 것이다.
장점은 아래와 같다.
첫째 스크립트의 유효성 검사 코드 대부분을 재활용할 수 있다.
둘째 스크립트는 XML 보다 훨씬 더 익숙하다.
셋째 스크립트는 로직을 가질 수 있다.
넷째 XML 에 비해 불필요한 중복을 줄일수 있다.
다섯째 스크립트는 순수 텍스트인 XML에 비해 자체적으로 테스트가 가능하다.

가장 큰 장점은 첫째와 셋째이다.
기존의 브라우저에서 사용되는 자바 스크립트를 사용할 수도 있고 펄이나 파이썬이 익숙하다면 그것도 상관이 없다.

script.js 파일은 기존의 자바 스크립트에서 복사해서 

<script.js>

   fvalid = bsf.lookupBean ("fvalid"); 
   m = bsf.lookupBean("m");

  fvalid.setForward("add") ;
  fvalid.required("subject", m.get("jmsg.validate_null", m.get("etc.subject")), true);
  fvalid.maxLengthByte("subject", 200, m.get("jmsg.length", m.get("etc.subject"), "1", "200"), true);
  // fvalid.notAllowed2ByteSpace("subject", m.get("jmsg.include_fullwidth_space", m.get("etc.subject")), true);
  fvalid.required("content", m.get("jmsg.validate_null", m.get("etc.cont")), true);
  ......

와 같이 저장한다.

실제 코드에서는 위와 같이 저장된 js 파일을 읽어서 실행하게 된다.




이를 테스트 할수 있는 코드는 아래와 같다.

package test.bsf;

import java.io.FileReader;

import junit.framework.TestCase;

import org.apache.bsf.BSFManager;
import org.apache.bsf.util.IOUtils;

import com.bleujin.framework.res.Resources;

public class TestFormValid extends TestCase {
  private BSFManager mgr = new BSFManager();

  TestContentBean bean ;
  private FormValid fvalid ;
  private Resources m ;
  public void setUp() throws Exception{
    bean = new TestContentBean() ;
    fvalid = new FormValid(bean;
    m = Resources.getResources("default";
    
    mgr.registerBean("fvalid", fvalid;
    mgr.registerBean("m", m;
  }


  public void testFalse() throws Exception {
    FormValid fvalid = runScript("D:\\eclipse\\workspace\\testAnother\\src\\vtest.js""add");
    assertEquals(false, fvalid.getResult("add").isValid()) ;
  }
  
  
  public void testTrue() throws Exception {
    FormValid fvalid = runScript("D:\\eclipse\\workspace\\testAnother\\src\\vtest.js""add");
    assertEquals(true, fvalid.getResult("").isValid()) ;
    
    bean.setSubject("abcd";
    bean.setContent("defg";
    assertEquals(true, fvalid.getResult("add").isValid()) ;
    
  

  private FormValid runScript(String fileName, String forwardNamethrows Exception{
    String language = BSFManager.getLangFromFilename(fileName)// get scripting language name
    FileReader in = new FileReader(fileName);
    String script = IOUtils.getStringFromReader(in)// read script from file

    mgr.exec(language, fileName, -1, -1, script)// execute scriptusing appropriate engine*
    return fvalid ;
  }
}



FormValid 객체는 기존의 자바 스크립트 코드를 흉내내어 만든 Java 객체이다.
대충 이런 형태이다.

package test.bsf;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.bleujin.framework.valid.IValueObject;
import com.bleujin.framework.valid.validator.MaxUTFByteLength;
import com.bleujin.framework.valid.validator.Required;
import com.bleujin.framework.valid.validator.IValidation;

public class FormValid {

  private Map<String, List<ValidRule>> rules = new HashMap<String, List<ValidRule>>();
  private String currentForwardName = "";
  
  private IValueObject valueObject ;
  public FormValid(IValueObject valueObject){
    this.valueObject = valueObject ;
    setForward("";
  }
  
  public void setForward(String forwardName){
    this.currentForwardName = forwardName ;
    rules.put(forwardName, new ArrayList<ValidRule>()) ;
  }
  
  public void required(String name, String errorMessage, boolean focus) {
    getCurrentSet().add(new ValidRule(new Required(valueObject, name), errorMessage, focus)) ;
  }

  public void maxLengthByte(String name, int maxByte, String errorMessage, boolean focus){
    getCurrentSet().add(new ValidRule(new MaxUTFByteLength(valueObject, name, maxByte), errorMessage, focus)) ;
  }

  public ValidResult getResult(String forwardName){
    
    List<ValidRule> currentRules = rules.get(forwardName;
    ValidResult result = new ValidResult();
    for(ValidRule rule : currentRules){
      if (! rule.getValidator().isValid()){
        result.addInvalidRule(rule;
      }
    }
    return result ;
  }

  private List<ValidRule> getCurrentSet(){
    return rules.get(currentForwardName)
  }

}

class ValidResult {
  
  List<ValidRule> lists = new ArrayList<ValidRule>() ;
  void addInvalidRule(ValidRule rule){
    lists.add(rule;
  }
  
  public boolean isValid(){
    return lists.size() == ;
  }
  
  public String getErrorMessage(){
    StringBuffer result = new StringBuffer() ;
    for(ValidRule rule : lists) {
      result.append(rule.getErrorMessage() "\n";
    }
    return result.toString() ;
  }
  
}

class ValidRule{
  
  private IValidation validator ;
  private String errorMessage ;
  private boolean focus ;
  ValidRule(IValidation validator, String errorMessage, boolean focus){
    this.validator = validator ;
    this.errorMessage = errorMessage ;
    this.focus = focus ;
  }
  
  public IValidation getValidator() {
    return validator;
  }
  public String getErrorMessage() {
    return errorMessage;
  }
  public boolean isFocus() {
    return focus;
  }
  
}

'Framework > Validation' 카테고리의 다른 글

Framework - Validation  (0) 2009.02.17
Posted by bleujin
Framework/Validation2009. 2. 17. 05:08

자비를 7년정도 SQL을 다룬지는 10년이 넘었지만 그럼에도 개인적으로 가장 자신있는 언어가 무엇이냐고 묻는다면 아마도 Javascript라고 말하지 않을까 싶다. XP의 김창준씨는 개발자는 1년에 최소한 한개의 언어를 익히는게 좋다라고 하는데 굳이 익히려 노력하지는 않았지만 그 동안 다룬 언어는 Pascal부터 시작해서 C, VB, C#, Java등이 있고 사용한 Web Script 언어는 ASP나 PHP, Perl 등등 어쩌다 보니 여러가지 언어를 해보게 되었다. 그리고 그 어떤 언어보다 Javascript의 간결함과 우아함을 좋아한다.

프로그래머는 태생적으로 간결함의 미학을 좋아하는 것 같다. 다만 그 간결함을 파이썬이나 루비 등에서 찾곤 하는데 - 예전에는 자바도 그런 미학이 있었는데 최근의 자바는 너무 살이 쪘다. - 개인적으로는 Java하고 비슷한 문법이라 그런지 모르겠지만 Javascript가 더 재미있다.


보통의 초보 프로그래머들이 작성하는 Form Validation 코드는
var frm = document.forms[0] ;
if (frm.subject.value == '')  {
    alert ('글 제목이 비었음') ;
    frm.subject.focus() ;
} else if (form.subject.value.length > 200 ) {
    alert('글제목은 200자까지 가능') ;
    frm.subject.focus() ;
} else if (..... )
   ,,,,,,

}
frm.submit() ;
... 런식인데 위 코드는 몇가지 문제를 가지고 있다.

첫번째 복잡하다. 앞의 글에서 복잡하다와 어렵다는 다르다고 말한적이 있는데 위 식으로 작성된 코드는 매우 쉽지만 아주 아주 복잡한 코드가 된다. 복잡한 이유는 대부분의 경우 중복이 많기 때문이다. 중복이라는 것은 단순히 코드의 중복만을 말하는 것은 아니다. 실제의 프로그램에서는 위와 거의 동일한 수십개의 코드를 작성해야 겠지만 위 코드만을 본다면 코드 자체만으로는 중복이 되지 않았다. 다만 구조적인 중복이 있다. 위 코드는 하나의 if 절이 모두
 1) if 조건 확인
 2) 조건이 달성되지 않았을때 어떤 액션을 함
      alert 창을 띄움
      focus를 맞춤
이라는 구조적인 중복이 있다. 코드의 중복은 라이브러리로 해결이 가능하지만 - 이를테면 getLength(formElementName) 같은 함수로 해결할 수 있겠지만 - 구조적인 중복은 대부분 프레임워크로 해결이 되어야 한다.

두번째로 위 코드는 활용적인 면에서도 그닥 실용적 - 요즘에는 실용이라는 말에 거부감이 생기지만 -ㅅ- 이지 않다. 예컨데 i18n의 다국어 메시지 처리도 곤란하고 다른 라이브러리나 프레임워크와도 호환이 좋지 않다.



그래서 꽤 오래전에 작성한 코드지만 위와 같이 js를 만들어서

  fvalid.setForward('add') ;
  fvalid.required("subject", "<%= m.get("jmsg.validate_null", m.get("etc.subject") ) %>", true);
  fvalid.maxLengthByte("subject", 200, "<%= m.get("jmsg.length", m.get("etc.subject"), "1", "200") %>", true);
  fvalid.notAllowed2ByteSpace("subject", "<%= m.get("jmsg.include_fullwidth_space", m.get("etc.subject") ) %>", true);
  fvalid.required("content", "<%= m.get("jmsg.validate_null", m.get("etc.cont")) %>", true);

런식으로 사용하는데 그 기본원리는 간단하다.

여러가지 효율상의 이유로 자바의 경우 쓰레드를 사용해서 invokation와 execution를 분리시키는데 위 코드는 declaration과 execution을 분리한 것이다. 이런 방식을 선언적 프로그래밍 이라고도 할 수 있는데 이렇게 해서 얻을 수 있는 장점은 구조적인 중복을 없엘수 있는것과 다른 코드와 꽤 접착이 잘된다는 점이다.

fvalid.required 메소드를 호출했을때 호출 당시에 바로 실행되어 subject의 유효성을 확인 하지 않는다. 다만 subject는 빈 내용이 되어서는 안된다는 정보가 객체 형태로 변환되어 어딘가에 저장될 뿐이다. 어찌보면 execution을 executable infomation으로 바꾼다는 점에서는 command 패턴과 유사하게 보일 수도 있지만 목적이 다르다.

이렇게 선언이 되면 실제 유효성의 실행은 fvalid.isValid() 함수를 호출함으로써 이루어진다.



어쨌거나....Javascript 얘기를 하려던건 아니고..  이 글의 본래 주제는 Java에서는 Validation을 어떻게 처리해야 하는가이다. 찾아보면 그 쓰임의 양에 비해서 의외로 쓸만한 코드가 거의 없다. Spring 모듈에 포함되어 있는 Validation이 그나마 가장 많이 알려지긴 했지만 이는 몇가지 문제가 있다.

Spring에서 Validation을 할수 있는 방법은 크게 2가지인데 하나는 자바 코드에 직접 사용하는 Case1) Programmatic 방법이고 다른 하나는 XML 설정파일을 이용하는 Case2) Declarative 방법이다.

Case1 ) 
    public void validate(Object object, Errors errors) {
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "email", "required", "Enter your email");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "required", "Enter your password");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "confirmPassword", "required", "Enter the same password for confirmation");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "confirmMember", "required", "Enter ajn member code");
        ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "required", "Enter your name");
    }


Case 2)
<bean id="caseSwappingValidator" class="org.springmodules.validation.ValangValidatorFactoryBean">
 <property name="syntax">
  <value>    <![CDATA[ { name : alterCase(?) = 'sTEVEN' : 'Name must be Steven' }  ]]>  </value>
       </property>

 <property name="customFunctions">
     <map>
         <entry key="alterCase" value="com.apress.expertspringmvc.validation.AlterCaseFunction" />
  </map>
 </property>
</bean>


근데 2가지 방법 모두 별로다 -ㅅ-;

첫째로 앞의 다른 글에서 언급한적이 있지만 Validation 정보는 대부분 구성 정보에 가깝기 때문에 자바 코드에서 직접 확인하는 것은 바보같은 짓이다. 

두번째로 그래서 Bean의 XML 설정을 하기도 하는데 게시판 하나 달랑 있는 프로그램이라면 모르겠지만 그 양이 만만찮아서 XML 파일을 작성하기가 쉽지 않을뿐더러 그 대다수가 중복 정보이다. 실수로 오타라도 있다면 실제 에러가 발생하기 전까지는 거의 확인이 어렵다.

세번째로 Spring에 너무 긴밀히 통합이 되어 있어서 접착성이 많이 떨어진다. 더군다나 이 모듈을 활용하려면 HTML 작성시 id attribute 등의 일정한 약속을 따라야 한다. 

첫째 문제는 언급할 필요도 없고 사실 가장 큰 문제는 2번째인 지나친 XML 파일의 비대화이다. Framework가 유행을 타면서 원칙대로 구성정보를 설정할 수 있도록 XML 파일을 많이 활용하긴 하지만 지나친 욕심으로 일반화를 너무 많이 추구한 나머지 상대적으로 너무 많은 정보를 구성 정보에 적어야 작동할 수 있게 되어 버렸다. 

대규모 재사용은 소규모 재사용과 그 의미가 많이 다르다라고 이전에 말한적이 있는데 어느 규모이상의 코드에서 재사용의 활용도를 높이면 높일수록 이런 반작용이 더욱 심해진다. 즉 이것은 Spring Framework 개발자의 잘못은 아니지만 SpringFramework의 덩치가 커져서 더 일반화를 가지려고 할수록 실제 활용도는 오히려 떨어진다는 말이다.


그래서 좀더 간결한 방법을 찾을 필요가 있다.


다음에 계속....


'Framework > Validation' 카테고리의 다른 글

Framework - Validation(Java)  (0) 2009.02.18
Posted by bleujin