Typescript로 다시 쓰는 GoF - Facade

아홉번째태양·2023년 10월 10일
0

Facade란?

파사드Facade 패턴은 복잡한 내부로직을 하나의 인터페이스로 정리한다. 추상화Abstraction과 혼동되기도 하는데, 파사드를 추상화의 한 방법으로 볼 수도 있지만 반대로 모든 추상화가 파사드 패턴인 것은 아니다.

파사드와 추상화의 차이

추상화는 보다 보편적인 개념으로, 단순히 구체적인 로직들을 숨기는데 그 목적이 있다. 반면, 파사드 패턴은 복잡한 로직을 하나의 인터페이스로 묶기 위해 사용한다.

예를들어, 유저의 중복을 검사하는 checkUserDup이라는 함수와 유저 정보를 저장하는 saveUser라는 함수가 있다고 하자. 이때, 각각의 함수는 내부적으로 보다 구체적인 로직을 숨기고 있으니 추상화를 했다고 할 수 있다. 하지만 이 둘이 회원가입을 위해 항상 함께 쓰인다면 이를 호출하는 단에서 매번 둘 다 사용하기보다 registerUser라는 하나의 인터페이스로 묶는 것이 파사드 패턴이다.

function checkUserDup(user: User) {}

function saveUser(user: User) {}

function registerUser(user: User) {
	const isDup = checkUserDup(user);
	if (!isDup) saveUser(user);
}

물론, checkUserDupsaveUser도 내부적으로 여러 인터페이스들을 묶고 있을 수 있다. 그렇기 때문에 파사드 패턴은 재귀적으로 사용될 수 있다. 하지만, 각각의 인터페이스가 무엇인지 정의하는 것이 중요한게 아니다. 어떤 목적을 가지고 코드를 작성하느냐가 파사드와 추상화의 차이다.

Facade 구현

호출자를 제외한다면 파사드 패턴에는 크게 두 가지 역할이 있다.

  1. Facade 파사드
    높은 수준의 단순한 인터페이스를 제공한다.
  2. SubSystem 서브시스템
    그 밖의 파사드가 묶고 있는 모든 인터페이스와 객체들은 서브시스템이 된다.

파사드 패턴은 레이어 아키텍쳐를 채용하는 api 서버라면 꽤 자주 쓰이는 패턴이다. 로그인을 하려는 상황을 파사드 패턴으로 정리해보자.

SubSystem

먼저, 로그인 정보를 비교하고 유저 정보를 가져오는 서비스와 로그인 사실을 저장하는 세션 서비스를 만든다.

class UserRepository {
	private readonly db: User[] = [];

	findUser(id: string, pwd: string): User|undefined {
		return db.find(user => user.id === id && user.pwd === pwd);
	}
}

class SessionService {
	private readonly cache: User[] = [];
	
	setSession(user: User): void {
		cache.push(user);
	}
}

여기서 만약 파사드 패턴을 안쓴다면 로그인 요청을 처리할 때 이렇게 써야할 것이다.

const userRepository = new UserRepository();
const sessionService = new SessionService();

user.post('/signin', (req, res) => {
	const { id, pwd } = req.body;
	const user = userRepository.findUser(id, pwd);
	if (user) {
		sessionService.setSession(user);
		res.sendStatus(201);
	} else {
		res.sendStatus(404);
	}
});

간단한 예제이기 때문에 엔드포인트에서 수행하는 로직이 단순하지만, 만약 더 많은 객체들의 라이프사이클을 관리하고 또 더 많은 메소드들을 호출해야한다면 무엇을 하는 코드인지 다소 읽기가 어려워진다.

Facade

이를 파사드 패턴으로 정리하는 것을 이런 것을 의미한다.

class UserService {
	private readonly userRepository = new UserRepository();
	private readonly sessionService = new SessionService();

	signin(id: string, pwd: string) {
		const user = userService.findUser(id, pwd);
		if (user === undefined) return false;

		sessionService.setSession(user);
		return true;
	}
}

로그인을 위한 객체들을 묶어서 새로운 객체를 만들고, 호출자가 신경써야하는 인터페이스를 단순화한다.

const userService = new UserService();

user.post('/signin', (req, res) => {
	const { id, pwd } = req.body;
	const success = userService.signin(id, pwd);
	res.sendStatus(success ? 201 : 404);
});

그리고 엔드포인트, 즉 호출자에서는 로그인을 위해 직접 어떤 객체들을 사용해야하는지 알 필요 없이 보다 단순한 signin이라는 새로운 인터페이스에 필요한 데이터를 넘겨주고 결과만 받아오면 된다.

주의할 점

파사드 패턴의 핵심은 인터페이스의 숫자를 줄이는데 있다. 코드를 작성한 당사자라면 사실 굳이 파사드 패턴을 쓰지 않아도 A라는 클래스를 사용하기 전에 B라는 클래스를 먼저 사용해야함을 알 수 있다. 하지만, 이렇게 다수의 객체가 하나의 동작을 위해 서로 묶여 있다면 파사드 패턴을 적용하여 해당 동작을 수행하는 입장에서 사용하는 인터페이스를 줄이는 것이 가독성을 증가시키며, 해당 호출자와 동작을 수행하는 여러 객체들 간의 결합도와 의존성을 낮춘다.

다만, 파사드는 만능이 아니다. 유지보수와 가독성을 대가로 코드의 복잡도가 올라가고 또 경우에 따라서는 성능 저하도 발생할 수가 있다.

참고자료

Java언어로 배우는 디자인 패턴 입문 - 쉽게 배우는 Gof의 23가지 디자인패턴 (영진닷컴)

Refactoring Guru - Facade

Software Design Pattern Cookbook - 4.2.5 Facade

0개의 댓글