자바 성능 튜닝 이야기 - 디자인 패턴

ddindo·2022년 10월 28일
1
post-thumbnail

들어가기 전...

예전에 개발을 할 때 API에 대한 성능에 대해 크게 고려 해본 적이 없었다. 딱히 고민 하지 않아도 데이터가 많지 않아 어떻게든 적당히 돌아갔기 때문이다.
최근에 인턴을 하며 성능이 느린 API 에 대한 개선을 맡았다. 데이터가 2만개 정도만 넣어도 1분 정도 걸리는 API 였다. 조물딱 거리다 보니 어떻게 속도를 줄이긴 했는데, 하는 과정에서 많은 개념을 알게 됐다. 멀티스레드 처리를 고민했고, 쿼리 실행 계획을 확인 하며, API의 속도도 측정해봤다. 이런 과정에서 돌아가게만 짜는게 아니라 잘 짜는게 중요하다는 걸 다시금 깨달았다. 그래서 집에 가는 길에 중고 서점에 들렀다. 원래는 Effective Java 책을 사서 보려했는데, 해당 도서가 없어서 둘러 보던 중에 눈에 띄게 된게 이 책이다.

디자인 패턴

책의 가장 처음에 디자인 패턴에 대해 설명을 했다. 전에 정보처리기사를 공부하며 GoF 디자인 패턴에 대해 공부를 했지만 사실 다 까먹었다. 개발하면서 봤던 MVC, Proxy, Decorator, Observer, Singleton 정도는 기억하고 있지만 사실 패턴이 너무 많았다.
결국 디자인 패턴이란 그동안 개발했던 사람들이 설계를 할 때 상황에 따라 가장 효율적으로 만들 수 있는 방법을 정리해 놓은 것이라고 생각한다. 그걸 야무지게 정리한 것이 GoF 디자인 패턴이다.

책에서 소개한 패턴은 총 4가지 였다.
MVC 패턴, JSP MVC 모델1, JSP MVC 모델2, J2EE 이다.
사실 MVC, JSP MVC 모델1, 2 까지는 책에서 본 걸로 충분히 이해할 수 있었다.
그런데 J2EE 패턴을 설명 하는 데 조금 복잡해 보여서 어지러웠다...

MVC 패턴

책의 가장 처음에 나온 패턴이자 내가 학부 때 가장 먼저 배운 패턴이다.
흔히 Model-View-Controller로 구성됐고, Model에서는 비즈니스 로직을 처리하고 View는 사용자에게 보여지는 부분 그리고 Controller가 중간에서 연결하는 역할을 한다고 알고 있다.

이는 Spring MVC 같이 다양한 어플리케이션에서 볼 수 있는 디자인 패턴이다.


JSP MVC 모델1

JSP MVC 모델1의 경우 JSP에서 View와 Controller의 역할을 수행한다.
JSP란 Java Server Page를 의미하고 간단하게 HTML코드에 Java 코드를 추가로 삽입하여 동적으로 처리할 수 있게 만들어 준것이다.

이렇게 처리할 수 있지만, 책에서는 이렇게 되면 코드가 길어질 때 유지보수에 엄청난 어려움이 발생한다고 말한다. 아무리 생각해도 하나에 다 집어 넣으면 그렇게 될거 같아 보이긴 한다.



JSP MVC 모델2

모델1과 다른 점은 Servlet이 추가된 것이다. JSP가 담당하던 Controller와 View 역할을 분리하여 client는 Servlet에 요청을 보내게 된다. 즉, Servlet이 Controller의 역할을 담당하게 되는 것이다. 이로써 모델1 보다 단순해져서 유지보수나 분업에 용이해졌다.

책에서 디자인이 중요한 이유는 한 명의 개발자가 만든 어플리케이션을 만든 사람이 끝까지 유지보수 하는 것이 아니라 결국 다른 사람의 손을 거쳐야 하는데 모델1과 같이 작성되면, 새로운 사람은 기존의 코드를 유지보수 할 때 힘들어 진다고 말한다.


J2EE

사실 위의 JSP MVC 모델도 처음 들어보는 개념이었지만, 비교적 쉬운 구조로 쉽게 이해할 수 있었다. 그런데 J2EE를 보는 순간 정신이 아득해졌다. 구조가 생각보다 복잡해 보였기 때문이다.
그래도 중복되는 코드가 많았고, 하나하나 설명을 달아놓았기 때문에 한 번 이해 해보려 했다.

core je22 patterns - http://www.corej2eepatterns.com/

보기만 해도 현기증이 난다. 그래도 책에 정리된걸 다시 한 번 정리해보려 한다.

이 그림은 위에서부터 사용자의 요청이 처리되는 순서라고 한다. 위에서 부터
프레젠테이션 티어 -> 비즈니스 티어 -> 인테그레이션 티어 라고 한다.
쉽게 말하자면, Front - Server - DB 정도로 나눌 수 있을거 같다.

Intercepting Filter

요청 타입에 따라 다른 처리를 하기 위한 패턴

Front Controller

요청 전후에 처리하기 위한 컨트롤러를 지정하는 패턴

View Helper

프레젠테이션 로직과 상관 없는 비즈니스 로직을 헬퍼로 지정하는 패턴

Composite View

최소 단위의 하위 컴포넌트를 분리하여 화면을 구성하는 패턴

Service to Worker

Front Controller와 View Helper 사이에 디스패처를 두어 조합하는 패턴

Dispatcher View

Front Controller와 View Helper로 디스패처 컴포넌트를 형성한다. 뷰 처리가 종료될 때까지 다른 활동을 지연한다는 점이 Service to Worker 패턴과 다르다.

Business Delegate

비즈니스 서비스 접근을 캡슐화하는 패턴

Service Locator

서비스와 컴포넌트 검색을 쉽게 하는 패턴

Session Facade

비즈니스 티어 컴포넌트를 캡슐화하고, 원격 클라이언트에서 접근할 수 있는 서비스를 제공하는 패턴

Composite Entity

로컬 엔티티 빈과 POJO를 이용하여 큰 단위의 엔티티 객체를 구현

Transfer Object

일명 Value Object 패턴이라고 많이 알려져 있다. 데이터를 전송하기 위한 객체에 대한 패턴

Transfer Object Assembler

하나의 Transfer Object로 모든 타입 데이터를 처리할 수 없으므로, 여러 Transfer Object를 조합하거나 변형한 객체를 생성하여 사용하는 패턴

Value List Handler

데이터 조회를 처리하고, 결과를 임시 저장하며, 결과 집합을 검색하여 필요한 항목을 선택하는 역할

Data Access Object

일명 DAO라고 많이 알려져 있다. DB에 접근을 전담하는 클래스를 추상화하고 캡슐화한다.

Service Activator

비동기적 호출을 처리하기 위한 패턴



사실 위의 내용을 보며 Spring에 대한 생각이 많이 났다. Spring의 내부 구조를 공부하며 Front Controller나 Intercepting, Filter 등의 구현을 볼 수 있기 때문이다. 그래서 찾아보니 Spring 또한 J2EE를 따른다는 사실을 알게 됐다.

책에서는 이 많은 패턴 중에 성능과 관련된 Transfer Object, Service Locator에 대해 중점적으로 설명한다.


Transfer Object

데이터를 전송하기 위한 객체이다. 흔히 DTO(Data Transfer Object)라고 한다.
사실 책에는 VO(Value Object)라고 써있으나, getter와 setter를 사용해 데이터를 수정한다는 점에서 VO의 불변성을 만족할 수 없어서 DTO라고 생각한다.

주로 속성을 private로 만든 뒤 getter setter를 사용하여 데이터에 접근할 수 있게 작성한다. 성능을 위해서 public으로 작성하면 더 빠르긴 하지만, 만약 속성 값이 null일 때 검사를 추가적으로 요구 하므로 보안적인 측면과 편의성을 위해 gettter setter를 사용한다.

또한 toString 메서드를 정의하여 사용하는 걸 추천했다. 디버깅을 하다보면, 해당 객체를 출력할 때 TestTO@c1716 처럼 알 수 없는 값이 나오기 때문이다.

하지만 사실 성능에 그렇게 큰 영향을 주지는 않는다고 한다.

Service Locator

자료를 찾아보니 스프링에서 DI를 통해 Dependency를 낮춘 것처럼 Service Locator를 사용하여 Dependency를 낮춘다고 한다.
즉, 코드 간의 결합도를 느슨하게 한다는 것이다.

또한 과거 EJB에서 DataSource를 찾을 때(lookup) 소요되는 응답속도를 낮추기 위해 사용 됐다고 한다.
cache라는 Map 객체에 home객체에서 찾은 결과를 보관하다 필요에 따라 제공하고, 해당 객체가 cache에 없으면 메모리에서 찾는 형식이다.

public class ServiceLocator {
	private InitialContext ic;
	private Map cache;
	private static ServiceLocator me;
	static {
		me = new ServiceLocator();
	}
	private SerivceLocator() {
		cache = Collections.synchronizedMap(new HashMap());
	}

	public InitialContext getInitialContext() throws Exception {
		try{
			if( ic == null) {
				ic = new InitialContext();
			}
		} catch (Exception e) {
			throw e;
		}
		return ic;
	}

	public static ServiceLocator getInstance(){
		return me;
	}
    // 생략
 }

Business delegater

책에서는 위에서 언급한 2개의 패턴에 대한 자세한 설명을 제공했지만, 그 외에 몇 가지 패턴에 대해 추가적으로 알아야 한다고 강조했다.

Business delegater는 프레젠테이션 영역과 비즈니스 계층을 분리하는 역할을 맡는 다고 한다.

위의 그림을 봐도 프레젠테이션 영역에서 Business delegater를 통해 비즈니스 로직에 접근하는 모습을 볼 수 있다. 추가적으로 Business delegate가 Service Locator에게 요청 받은 서비스를 lookup 하는 작업을 위임한다고 한다. 즉, Business service가 Service Locator를 통해 요청 받은 서비스를 찾고 프레젠테이션 영역에 돌려 준다고 볼 수 있다.

스프링의 경우 해당 패턴의 중요성이 떨어졌다고 한다. 이는 위에서 언급한 것 처럼 Business delegate는 Service Locator를 사용하여 서비스를 lookup 한다. 하지만, 스프링의 경우 Serivce Locator를 사용하지 않고, DI를 통해 서비스를 주입하여 결합도를 낮춘다.

Sessoin Facade

You want to expose business components and services to remote clients.

- Core J2EE Patterns -

Session Facade가 하는 역할은 여러 서비스나 로직을 하나로 묶어 놓은 뒤 외부에서 이를 접근할 때 Session Facade를 통해 접근할 수 있도록 하는 것이다.

아까와 같이 위의 그림을 다시 보면 Business delegate가 Service Locator를 통해 Service를 lookup 했다면, Session Facade를 통해 찾은 서비스를 접근할 수 있게 되는 것이다.

Session Facade는 Application Service, Business Object, DAO 같은 것에 접근할 때 사용할 수 있다.

Data Access Object

비즈니스 영역과 인테그레이션 영역을 분리하기 위해 사용 되는 패턴이다.
DB에 접근하는 로직은 전부 이 DAO를 통해 실행되게 된다.

DAO는 Data Source에 연결되어 connection을 가져오고 이를 통해 DB에 접근하여 원하는 작업을 수행하게 된다. 이 때 만약 데이터를 가져오게 된다면, TO를 통해 데이터를 담아 가져오는 방식으로 사용된다.

Reference

http://www.corej2eepatterns.com/
https://stackoverflow.com/questions/33238791/business-delegate-pattern-with-spring-controllers

1개의 댓글

comment-user-thumbnail
2022년 10월 28일

좋은 이야기 감사합니다.

답글 달기