- 이 글은
JavaEE 디자인패턴 - 무라트 예네르, 알렉스 시돔 지음
책을 공부하며 작성한 글입니다.
1. 디자인 패턴이란?
- 공통적인 애플리케이션 설계 문제의 해법
- 객체지향 프로그래밍에서는 대단위 소프트웨어 아키텍처보다는 보통 객체 생성 및 객체 간 상호 작용 문제를 해결하는 데 주로 사용
- 절차형 프로그래밍 언어로 개발할 때와 달리 객체 지향형 프로그래밍은 문제가 있었음 ( 무엇이 잘 돌아가고 잘 안돌아가는지 조차 짚고 넘어갈 틈 없이 빠르게 세대교체가 이루어짐 )
- 절차형언어는 스파게티 코드같은 큰 이슈를 많이 해결했으나, 객체지향에서는 문제점이 드러나지 않음
- 문제들은 대개 유사한 패턴을 지니고 있기 때문에 GoF가 패턴으로 묶어 틀을 정립
- 만능은 아니며, 프레임워크처럼 곧바로 꺼내쓸 수도 없음
- 과용하면 문제만 늘어나게 된다.
1-1. 디자인 패턴 유형
01. 생성패턴
- 객체생성, 초기화, 클래스 선택에 관한 패턴
- 싱글톤, 팩토리 …
02. 행동패턴
- 객체 간 소통, 메시징, 상호 작용에 관한 패턴
- 옵저버 …
03. 구조패턴
- 클래스와 객체 관계를 조직하는 패턴
- 관련된 객체를 서로 묶어 원하는 로직을 구현하는 방법을 안내한다.
- 장식자 …
1-2. 자바에서 엔터프라이즈 자바로
- J2EE : 자바 EE 5버전이 나오기 전 이름
- J2EE 1.4는 수년간 사랑받아왔지만 XML파일이 너무 길고 복잡했고, 프레임워크와 컨테이너 모두 가볍지 않음
- 이식성
- 운영체제에 구애받지 않고 JVM으로 실행할 수 있음
- 개발자가 Window OS에서 개발하고 Linux에서 테스트한 다음 UNIX 시스템에 완성품을 배포할 수 있다.
- 보안
- 트랜잭션
- J2SE언어 특성
- 구문이 쉽고 garbage collection을 대신하는 객체지향 프로그래밍 언어
1-3. 디자인패턴과 엔터프라이즈 패턴의 차이
- 엔터프라이즈 패턴은 엔터프라이즈 소프트웨어를 대상으로 하며, 데스크톱 애플리케이션과는 사뭇 다른 엔터프라이즈 문제 해결에 초점을 둔다는 점에서 디자인 패턴과 다르다.
- 서비스 지향 아키텍처 라는 새로운 사상에 따라 잘 조직된 재사용 가능한 엔터프라이즈 소프트웨어를 제작하려면 몇 가지 기본 원칙을 따라야 한다.
- 돈 박스의 SOA 4대 원칙
- 경계가 분명하다.
- 서비스는 자율적이다.
- 서비스는 스키마와 규약을 공유하나 클래스는 공유하지 않는다.
- 서비스 호환성은 정책에 따라 결정한다.
2. Java EE 기초
2-1. 다중 티어 아키텍처
- Java EE의 아키텍처
- 클라이언트 티어
- 미들티어 (웹 레이어 + 비즈니스 레이어)
- 엔터프라이즈 정보 시스템 티어

01. 클라이언트 티어
- 프리젠테이션 티어
- 클라이언트 - 서버 구조에서 클라이언트에 해당하는 애플리케이션 전부를 일컫는다.
- 하이퍼텍스트 전송 프로토콜 (HTTP)로 자바 EE 서버에 접속하는 브라우저가 거의 대부분
- 클라이언트 애플리케이션이 리소스를 서버에 요청하면 서버는 요청을 받고 응답을 준다.
02. 미들티어
- Java EE 서버는 미들 티어에 둥지를 틀고 웹 컨테이너와 EJB 컨테이너라는 두 논리 컨테이너를 제공
- MVC 패턴은 웹 레이어의 뷰 생성과 비즈니스 레이어의 데이터 모델링을 명확히 구분짓기 위해 사용
02-1. 웹레이어
- 클라이언트 티어와 비즈니스 레이어 간 상호작용을 관장
- 클라이언트 티어는 웹 레이어에게 리소스를 요청
02-2. 비즈니스 레이어
- 업무요건을 처리하거나 도메인 내부의 특정 비즈니스 로직을 실행
- EIS티어에 있는 DB에서 데이터를 가져오고 클라이언트 데이터를 취한다.
03. EIS 티어
- Data tier, Persistence tier, Integration tier
- DB 형태의 데이터 저장 단위로 구성
- 데이터를 공급하는 리소스면 어느것이든 가능
- 오래된 레거시 시스템, 파일시스템
04. Java EE 서버
- 엔터프라이즈 애플리케이션에 필요한 자바 EE 기능을 제공
- 미들티어에 위치
2-2. Java 웹 프로파일
- 웹 기반 엔터프라이즈 애플리케이션 개발에 가장 적합한 기술로 구성한 하위 기술 집합
- 웹 애플리케이션 개발에 필요한 기술만 추려 플랫폼 덩치는 작고 덜 복잡
- 웹 프로파일은 작업 흐름 및 핵심기능(Servlet), 프리젠테이션(JSP, JSF), 비즈니스 로직(EJB 라이트), 트랜잭션(JTA), 퍼시스턴트(JPA), 웹소켓 등으로 구성된 온전한 기술 집합체
2-3. Java EE 핵심원리
- Convention over configurataion : 설정보다 관례
- 코드의 목표에 집중하고 유연함을 잃지 않고 개발을 단순화하는 중심 설계 사상.
- 엔티티, Java Bean, Managed Bean, Servlet, SOAP, REST형 웹 서비스 등의 컴포넌트 활용 (주입 가능한 의존체 )
- 느슨한 결합 : 확장성이 좋아져 기존 클래스를 새 클래스로 바꿀 때 다른 클래스까지 바꿀 필요가 없다.
- intercepteor는 비즈니스 관심사를 기술관심사(성능) 및 횡단 관심사(보안)로부터 떼어놓는다.
3. 퍼사드패턴
- 하위 시스템의 인터페이스 세트에 일관된 인터페이스를 제공하는 것
- 하위 시스템의 복잡도를 감추는 동시에 그 전체 기능을 사용하기 쉬운 인터페이스로 제공
- 사용 예
- legacy back-end 시스템에 단일 접근 창구 제공
- 클래스에 드라이버 같은 퍼블릭 API를 만든다.
- 거시적으로 서비스에 접근하는 창구를 둔다.
- 네트워크 호출을 줄인다.
- 퍼사드는 하위 시스템에 여러 차례 호출하는 반면, 원격 클라이언트는 퍼사드를 한 번만 호출
- 보안과 단순함 측면에서 애플리케이션 내부 상세 및 실행 흐름을 캡슐화
- 싱글톤 추상 팩토리
3-1. 일반 자바코드로 구현한 퍼사드 패턴
public class WashingMachine{
public void heavilySoiled() {
setWaterTemperature(100);
setWashCycleDuration(90);
setSpinCycleDuration(10);
addDetergent();
addBleach();
///...
}
public void lightSoiled(){
setWaterTemperature(100);
setWashCycleDuration(90);
setSpinCycleDuration(10);
addDetergent();
heatWater();
startWash();
}
}
//퍼사드 사용
new WashingMachine().lightlySoiled();
- 퍼사드에 있는
heavilySoiled()
또는 lightlySoiled()
메소드를 호출하면 빨래를 돌릴 수 있음
- 메소드 구현체는 클라이언트와 완전히 분리되어 있어서 구현체를 바꾸어도 클라이언트에는 영향이 없다.
퍼사드 패턴의 장점
- 클라이언트가 하위 시스템을 알 필요가 없으므로 결합도가 낮아짐
- 코드 변경시 유지보수성, 관리성이 좋아짐
- 로직을 다시 사용할 수 있어 기능을 재사용할 일이 많아진다.
- 여러 번 실행해도 호출하는 메서드는 동일하기 때문에 일관된 서비스가 보장된다.
- 연관된 메서드를 한 메서드로 묶어 호출하므로 비즈니스 로직이 덜 복잡해진다.
- 보안 및 트랜잭션 관리를 중앙화 한다.
- 테스트할 수 있고 mock을 쓸 수 있는 패턴으로 구현한다.
3-2. Java EE로 구현한 퍼사드 패턴
- Java EE는 퍼사드 패턴의 구현체를 기본 제공하지 않지만 상태성 / 무상태성 EJB를 이용하면 알기 쉽게 구현할 수 있다.
- EJB를 쓰면 퍼사드에서 필요할 지 모를 다른 EJB에 쉽게 접근할 수 있다.
01. 무상태성을 지닌 퍼사드
@Stateless
- 기능은 연관되어 있지만 별개의 bean으로 나뉘어진 3개의 EJB가 있다
- CustomerService
- 로그인한 고객 ID를 조회하는 메소드 (getCustomer)
- 고객 ID가 올바른지 체크하는 메소드 (checkID)
- LoanService
- 고객의 신용등급이 해당 금액을 대출할 수 있는지 체크하는 메소드 (checkCreditRating)
- AccountService
- 계좌 잔액이 충분한지 확인하는 메소드 (getLoan)
- 계좌 잔액을 업데이트 하는 메소드 (setCustomer)
- 세 서비스 EJB의 연관된 기능을 묶어 퍼사드 패턴 구현
@Stateless
public class BankServiceFacade {
@Inject
CustomerService customerService;
@Inject
LoanService loanService;
@Inject
AccountService accountService;
public boolean getLoan(int sessionId, double amount) {
boolean result = false;
long id = customerService.getCustomer(sessionId);
if(customerService.checkId(id)){
if(loanService.checkCreditRating(id, amount)){
if(accountService.getLoan(amount)){
result = accountService.setCustomerBalance(id, amount);
}
}
}
return result;
}
}
- 호출의 계층 구조를 단순하게 만들 수 있다.
- 메소드 한 개만 호출해서 원하는 서비스를 받는 비대화형 케이스라면 메소드 중간에 대화 상태를 담아둘 필요가 없으므로 무상태성 세션 bean으로 구현하는 게 좋다.
02. 상태성을 지닌 퍼사드
@Stateful
- 복잡한 로직을 감추고 사용하기 쉬운 인터페이스를 클라이언트에게 표출하는 bean은 상태성 세션 bean 이나 싱글톤 bean으로 구현할 수도 있다.
- Java EE에 이르러 훨씬 간편해짐
- 메소드 호출 도중 대화 상태를 이어가야한다면 상태성 세션 bean으로 퍼사드를 구현하는 것이 좋음
- 상태성 세션 퍼사드는 클라이언트가 일부러 대화를 끝내거나 타임아웃이 나기 전에는 서버 리소스를 붙들고 있기 때문에 주의해야 한다.
- 요청할 때마다 클라이언트 세션 상태를 유지한 채 무상태성 퍼사드의 새 인스턴스를 생성하므로 재사용 X, 다른 클라이언트와 공유 X
3-3. 언제 어디에 사용?
- 퍼사드 패턴은 복잡한 비즈니스 로직을 상위 레벨에서 캡슐화하고 접근 지점을 명확하게 단일화하여 API를 통해서만 접근할 수 있게 유도한다.
- 다른 개발자에게 인터페이스나 API를 제공할 경우 로직이 얼마나 복잡한지 앞으로 변경될 가능성은 없는지 확인하자
- 바뀔지 모를 부분은 감추고 깔끔한 API를 제공할 때 사용
- 복잡한 하위 시스템 앞에 상위 레벨의 단순한 인터페이스를 제공
- 과용하면 레이어만 누누이 쌓인 더 복잡한 시스템이 될 수 있음.
4. 싱글톤 패턴
- 클래스가 인스턴스 하나만 갖게하고 전역 범위에서 이 인스턴스에 접근하는 단일 지점을 제공
- 보통 팩토리패턴과 함께 씀
- 사용 예
- 애플리케이션 도메인 전역에서 설정 데이터 등 공유 데이터에 접근
- 값비싼 리소스를 한 번만 읽고 캐시하여 전역 범위에서 접근 가능한 지점을 공유하고 성능을 높인다.
- 유일한 애플리케이션 로거 인스턴스를 생성한다.( 하나만 필요 )
- 팩토리 패턴을 구현한 클래스 내부에서 객체를 관리
- 유일한 퍼사드 객체를 생성( 하나만 필요 )
- 정적 클래스를 뒤늦게 생성한다( 싱글톤은 나중에 인스턴스화 할 수 있음 )
- 스프링은 Singleton을 bean을 생성하고 JavaEE는 서비스 위치자 등에 내부적으로 싱글톤을 사용한다.
- 싱글톤 남용시 쓸데없이 리소스를 캐시하고 가비지컬렉터가 객체를 회수하지 못해 메모리 리소스가 줄어든다.
- 객체 생성과 상속의 이점을 포기하는 것을 의미함.
- 과한 사용은 부실한 객체지향 설계가 되고, 메모리 및 성능 문제의 원인이 됨
4-1. 싱글톤 빈
@Singleton
을 붙이면 알아서 싱글톤 인스턴스 생성
- @PostConstructor를 붙인 메소드를 호출한 적이 없어서 싱글톤에 아무런 로그도 찍히지 않음
@Singleton
public class CacheSingletonBean {
private Map<Integer, String> myCache;
@PostConstructor
public void start(){
Logger.getLogger("MyGlobalLogger").info("시작한다!");
myCache = new HashMap<Integer, String>();
}
public void addUser(Integer id, String name) {
myCache.put(id, name);
}
public String getName(Integer id) {
return myCache.get(id);
}
}
4-2. 시동 시 싱글톤 사용
@Startup
은 인스턴스 시동 시점에 강제로 만드는 Annotation
- JavaEE싱글톤은 뒤늦게 초기화함.
- 서버 재실행시 @PostConstructor메소드가 호출됨.
@Startup
@Singleton
public class CacheSingletonBean {
private Map<Integer, String> myCache;
@PostConstructor
public void start(){
Logger.getLogger("MyGlobalLogger").info("시작한다!");
myCache = new HashMap<Integer, String>();
}
public void addUser(Integer id, String name) {
myCache.put(id, name);
}
public String getName(Integer id) {
return myCache.get(id);
}
}
4-3. 시동 순서 결정
@DependsOn
- 싱글톤이 다른 리소스에 의존한다면? 어떤 리소스가 준비된 연후에 싱글톤을 생성해야 한다면?
- DB에서 메시지 데이터를 조회하여 캐시하는 싱글톤일 경우 순서를 결정해야함.
@Startup
@DependsOn("MyLoggingBean")
@Singleton
public class CacheSingletonBean {
private Map<Integer, String> myCache;
@EJB
MyLoggingBean loggingBean;
@PostConstructor
public void start(){
Logger.getLogger("MyGlobalLogger").info("시작한다!");
myCache = new HashMap<Integer, String>();
}
public void addUser(Integer id, String name) {
myCache.put(id, name);
}
public String getName(Integer id) {
return myCache.get(id);
}
}
- 의존하는 bean이 많을 경우 여러개 지정도 가능함
- @DependsOn({“MyLoggingBean”, “MyInitializationBean”})
4-4. 동시성 관리
@ConcurrencyManagement
@Lock
을 붙여 접근 제어할 수 있다.
- @Lock(LockType.WRITE) : 메소드 실행 도중엔 다른 클라이언트가 bean에 접근하지 못하게 잠근다.(데이터 수정중 접근 차단)
- @Lock(LockType.READ) : 메소드 동시 접근을 허용하고 다른 클라이언트의 bean접근을 허용
- 시간내에 처리하지 못하면 ConcurrentAccessTimeoutException예외를 던진다. ( 시간 설정 가능 )
@AccessTimeout(value=120000)
: 기본 단위는 밀리초
@Startup
@DependsOn("MyLoggingBean")
@ConcurrencyManagement(ConcurrencyManagementType.CONTAINER)
@Singleton
public class CacheSingletonBean {
private Map<Integer, String> myCache;
@EJB
MyLoggingBean loggingBean;
@PostConstructor
public void start(){
Logger.getLogger("MyGlobalLogger").info("시작한다!");
myCache = new HashMap<Integer, String>();
}
@Lock(LockType.WRITE)
public void addUser(Integer id, String name) {
myCache.put(id, name);
}
@Lock(LockType.READ)
public String getName(Integer id) {
return myCache.get(id);
}
}
4-5. 싱글톤 패턴의 현재
- 싱글톤 패턴을 오남용했을 떄읨 누제점과 멀티스레드 애플리케이션에서 사용시 드러나는 단점 때문에 인기가 떨어짐
- 구현하기 쉬워서 개발자들의 오남용이 잦았다.
- 간단한 단위테스트를 하나 실행시에도 전역 상태값을 초기화해야해서 테스트가 점점어려워짐
- 다중 노드 환경에서 백 엔드에 위치한 스레드-안전하지 않은 리소스에 접근할 목적으로 bean을 사용하면 문제가 될 수 있다.
- 병목현상이 발생하고 클래스 간 강한 결합이 유발되는 현상은 또 다른 문젯거리이다.
- 충분한 성장을 해왔고 여전히 중요하고 발전성 있는 패턴