You will be fine

<토비의스프링> 7.3~7.4 서비스 추상화

by BFine
반응형

7.3. 서비스 추상화 적용 

 7.3.1 OXM 서비스 추상화

  -  보통 JAXB를 많이 쓰는 것 같은데 여기서는 4가지를 소개하고 있다.

        1. Castor XM

        2. JiBX

        3. XmlBeans

        4. Xstream 

  -  이렇게 XML과 객체를 매핑해서 변환해주는 기술을 OXM(Object-XML Mapping)이라 한다.

  -  UnmarshallerMarshaller인 이유는 Jaxb2Marshaller는 두 인터페이스 모두 구현하고 있기 때문이다.

  -  구현클래스만 빈으로 등록하면 코드 수정없이 구현 기술을 적용할 수 있다.

 

 7.3.2 OXM 서비스 추상화 적용 

  -  이름은 OxmSqlSevice로 하고 SqlRegistry를 DI 받게 만든다

  -  OXM 기술에 의존적이라고 해서 꼭 OXM 코드를 직접 가지고 있을 필요는 없다.

  -  OxmSqlServiceOxmSqlReader를 static private 클래스로 내장하여 자기만 사용할 수 있도록 한다.

  -  이렇게 제한해두는 이유는 무엇일까?

       => 하나의 클래스로 만들어두기 때문에 빈의 등록과 설정이 단순해지기 때문!

  -  디폴트 설정을 두는 방법도 있지만 내부에서 만드는 객체의 경우 외부에서 지정해주기가 힘들다.

  -  OxmSqlReader는 외부에 노출되지 않기 때문에 OxmSqlSevice에 의해서만 만들어지고 빈등록 불가하다.

        => 간접적으로 DI를 받아야한다.

  -  필드선언 없이 setter로 빈을 주입할 수 있다는 것을 새롭게 알게되었다.!!

public class OxmSqlService implements SqlService {

    private final OxmSqlReader sqlReader = new OxmSqlReader();
    
    ...
    
     
    // 단일빈 설정구조를 위한 창구 역할
    public void setUnmarshaller(Unmarshaller unmarshaller) {
        this.sqlReader.setUnmarshaller(unmarshaller);
    }

    public void setSqlmapFile(String sqlmapFile) {
        this.sqlReader.setSqlmapFile(sqlmapFile);
    }
    
    private static class OxmSqlReader implements SqlReader {
        private Unmarshaller unmarshaller;
        private String sqlmapFile;

    }
}

  -  SqlService의 핵심 메서드 구현 코드가 BaseSqlService와 동일함 (loadSql(), getSql())

  -  코드의 양이 많아지거나 복작해지면 양쪽다 함께 변경해야하는 문제가 발생한다.

  -  위임 구조를 이용해 코드의 중복을 제거할 수 있다.

        => 어댑터 같은 개념으로 BaseSqlService 앞에 OxmSqlService를 두는 설계 가능

  -  위임구조

         1. 두개의 빈을 등록한다.

         2. 클라이언트 요청을 직접 받는 빈을 확인한다.

         3. 이빈이 뒤의 빈에게 전달한다.

  -  이런 구조는 여기에는 적합하지 않고 OxmSqlSerivceBaseSqlService를 한 클래스로 묶어보자

  -  SqlSevice의 기능을 구현하는 것은 내부에 BaseSqlService를 만들어 위임한다.

public class OxmSqlService implements SqlService {

    private final BaseSqlService baseSqlSevice = new BaseSqlService();
 
    @PostConstruct
    public void loadSql() {
        this.baseSqlSevice.setSqlReader(this.sqlReader);
        this.baseSqlSevice.setSqlRegistry(this.sqlRegistry);

        this.baseSqlSevice.loadSql();
    }

    @Override
    public String getSql(String key) throws SqlRetrievalFailureException {
        return this.baseSqlSevice.getSql(key);
    }
}

 7.3.3. 리소스 추상화 

  -  Reader의 공통문제는 UserDao와 같은 클래스패스에 존재하는 파일로 제한된다는 것이다.

  -  스프링은 리소스 접근 API를 추상화해서 Resource라는 인터페이스를 정의했다.

public interface Resource extends InputStreamSource {
    // 리소스의 존재나 읽기 가능한지 여부, 입력스트림이 열려있는지 확인가능 
    boolean exists();
    boolean isReadable();
    boolean isOpen();

    // JDK의 URL, URI, File 형태로 전환 가능한 리소스에 사용됨
    URL getURL() throws IOException;
    URI getURI() throws IOException;
    File getFile() throws IOException;

    // 리소스에 대한 이름과 부가적인 정보 제공
    long lastModified() throws IOException;
    String getFilename();
    String getDescription();
}

public interface InputStreamSource {
    // 모든 리소스는 InputStream형태로 가져옴
    InputStream getInputStream() throws IOException;
}

  - 어떻게 임의의 리소스를 Resource 객체로 가져올 수 있을까?  

        => 다른 서비스 추상화 객체와는 달리 Resource이 아니라 으로 취급된다.

  -  구현클래스 문제는 스프링에는 접두어를 이용해 선언하는 방법이 있다.

        => 문자열 안에 리소스 종류와 위치를 함께 표현하고 이를 실제 타입으로 변환해주는 ResourceLoader

  -  접두어 예는 614쪽 표로 정리되어있음

  -  ResourceLoader의 대표적인 예는 스프링 어플리케이션 컨텍스트!

        => ApplicationContextResourceLoader 인터페이스를 상속

private class OxmSqlReader implements SqlReader {
    // SQL매핑정보타입을 Resource로 변경
	private Resource sqlmap = new ClassPathResource("sqlmap.xml", UserDao.class);

	public void setSqlmap(Resource sqlmap) {
		this.sqlmap = sqlmap;
	}

	public void read(SqlRegistry sqlRegistry) {
		try {
			Source source = new StreamSource(sqlmap.getInputStream());
				
		} catch (IOException e) {
		}
	}
}

  -  Resource 객체가 실제 리소스는 아니라는 점 주의하자

  -  문자열로 지정할때는 리소스로더가 인식할 수 있는 문자열로 표현

<bean id="sqlService" class="springbook.user.sqlservice.OxmSqlService">

        <property name="unmarshaller" ref="unmarshaller" /> 

        <property name="sqlmap" value="classpath:/springbook/user/dao/sqlmap.xml" />
        // 클래스패스 위치를 정할때는 클래스패스 루트부터 절대위치를 적어야한다.

</bean>

  -  이외에도 file: http: 가능하다 

 

7.4 인터페이스 상속을 통한 안전한 기능확장

권장하지는 않지만 서버를 재시작하지 않고 SQL만 변경해야 할 수도 있다.

SQL 매핑정보 파일을 변경했다고해서 메모리상의 SQL정보가 갱신되진 않는다.   

 7.4.1 DI와 기능의 확장 

  -  DI를 적용하려면 커다란 객체하나만 존재해서는 안된다.

  -  최소한 두개 이상의 의존관계를 가지고 서로 협력해서 일하는 객체가 필요하다.

       => 적절한 책임에 따라 객체를 분리해줘야 한다.

  -  DI는 런타임시에 의존 객체를 다이나믹하게 연결해 유연한 확장을 꾀하는게 목적

       => 항상 확장을 염두에 두고 객체 사이의 관께를 생각해야함

  -  DI를 적용할때는 가능한 인터페이스를 사용하게 해야 한다.

  -  인터페이스를 사용하는 이유!!

        1. 다형성을 얻기 위해서

             => 하나의 인터페이스에 여러개의 구현을 바꿔가면서 사용

        2. 클라이언트와 의존객체 사이의 관계를 명확하게 해줄 수 있기 때문

             => 여러개의 인터페이스를 구현한 클래스의 클라이언트는 다를 수 있다.

  -  하나의 객체를 바라보는 창이 여러가지일 수 있다는 뜻 

  -  인터페이스 분리 원칙

        => 목적과 관심이 각기 다른 클라이언트가 있다면 인터페이스를 통해 분리하자

  -  클래스를 직접 참조하는 DI면 클라이언트에 특화된 의존관계를 만들 수가 없다.

인터페이스 사용이유가 단순히 다형성이라고 생각했었는데 관계의 측면도 있다는 사실을 알게되었다.
그렇지만 아직도 전혀 확장가능성 없는 로직에 대해서 Service -> ServiceImpl에 1대1구조를 왜 써야하는지는 잘 모르겠다.

물론 미래의 불확실성에 좀 더 가중치를 둔다면 예상치 못한기능이 추가되어 구현체가 필요하다거나 ServiceImpl을 그대로 남겨두고 ServiceImplV2를 만들어서 반영하거나 기존 관련없는 테스트코드도 수정할 필요가 없어지니 인터페이스를 만들어 두는 것에 대해 다시 한번 생각해보게 되었다.   

 7.4.2 인터페이스 상속 

  -  인터페이스를 여러개 만들어서 구분하는 이유는 다른 종류의 클라이언트가 등장하기 때문이다.

  -  클라이언트가 자신의 관심에 따른 접근 방식을 간섭없이 유지할 수 있다는 점이다.

        => 기존 클라이언트에 영향없이 기능 확장 및 수정 가능

  -  새로운 클라이언트를 위한 새로운 인터페이스가 필요하지만 SqlRegistry와 별개로 만들어야하는 것은 아니다.

반응형

블로그의 정보

57개월 BackEnd

BFine

활동하기