open:facade-pattern

Facade Pattern

새로운 멤버 유진 라인 주니어(Eugenio Reinn Jr.)가 처리 중인 서블릿에 134개 라인이 추가된 파일을 커밋했다. 하지만 추가된 코드들이 실제로 하는 일은 요청을 처리하는 것 뿐이었다. 즉 클래스들을 임포트해서 사용한 것이 전부이다. 이것은 한 줄짜리 커밋이어야 했다.

페드로: 얼마나 많은 줄이 커밋되었는지 누가 신경이나 쓰나요?

이브: 신경쓰는 사람이 있을 수 있죠.

페드로: 어디가 문제인지 봅시다.

class OldServlet {
  @Autowired
  RequestExtractorService requestExtractorService;
  @Autowired
  RequestValidatorService requestValidatorService;
  @Autowired
  TransformerService transformerService;
  @Autowired
  ResponseBuilderService responseBuilderService;

  public Response service(Request request) {
    RequestRaw rawRequest = requestExtractorService.extract(request);
    RequestRaw validated = requestValidatorService.validate(rawRequest);
    RequestRaw transformed = transformerService.transform(validated);
    Response response = responseBuilderService.buildResponse(transformed);
    return response;
  }
}

이브: 오 이런…​

페드로: 이것은 개발자를 위한 내부 API에요. 요청을 처리할 때마다, 서비스 4개를 써야 하는데, 관련된 모든 임포트를 포함해야 하고, 그래서 이런 코드가 된 거죠.

이브: 리팩토링을 해보죠…​그러니까…​

페드로: …​파사드 패턴. 모든 의존성(dependencies)을 하나의 접점으로 모아서 API를 단순화하죠.

public class FacadeService {
  @Autowired
  RequestExtractorService requestExtractorService;
  @Autowired
  RequestValidatorService requestValidatorService;
  @Autowired
  TransformerService transformerService;
  @Autowired
  ResponseBuilderService responseBuilderService;

  RequestRaw extractRequest(Request req) {
    return requestExtractorService.extract(req);
  }

  RequestRaw validateRequest(RequestRaw raw) {
    return requestValidatorService.validate(raw);
  }

  RequestRaw transformRequest(RequestRaw raw) {
    return transformerService.transform(raw);
  }

  Response buildResponse(RequestRaw raw) {
    return responseBuilderService.buildResponse(raw);
  }
}

페드로: 그래서 어떤 서비스, 혹은 일단의 서비스가 필요하게 되면 단지 파사드만 쓰면 되죠.

class NewServlet {
  @Autowired
  FacadeService facadeService;

  Response service(Request request) {
    RequestRaw rawRequest = facadeService.extractRequest(request);
    RequestRaw validated = facadeService.validateRequest(rawRequest);
    RequestRaw transformed = facadeService.transformRequest(validated);
    Response response = facadeService.buildResponse(transformed);
    return response;
  }
}

이브: 잠깐만요, 방금 모든 의존성을 한 곳으로 옮기고, 매번 그것을 사용한다…​라는 거죠?

페드로:네, 어떤 기능이 필요할 때마다, FacadeService를 사용하죠. 필요한 의존성은 이미 거기에 있으니까요.

이브: 하지만 중개자(Mediator) 패턴에서도 같은 일을 했잖아요?

페드로: 중개자는 행위 패턴이죠. 모든 의존성을 중개자에게 몰아넣고, 거기에 새로운 행위를 추가하죠.

이브: 그러면 파사드는?

페드로: 파사드는 구조 패턴이죠, 파사드 패턴에는 새로운 기능을 추가하지 않아요, 그냥 기존 기능들을 노출할 뿐이죠.

이브: 알겠어요. 하지만 그런 작은 차이에 붙이는 이름 치고는 정말 별나게 거창한 이름이군요.

페드로: 아마도.

이브: 다음 클로저 코드는 이름공간들을 가져와서 사용하고 있어요.

(ns application.old-servlet
  (:require [application.request-extractor :as re])
  (:require [application.request-validator :as rv])
  (:require [application.transformer :as t])
  (:require [application.response-builder :as rb]))

(defn service [request]
  (-> request
      (re/extract)
      (rv/validate)
      (t/transform)
      (rb/build)))

이브: 파사드로 모든 서비스를 노출시키고

(ns application.facade
  (:require [application.request-extractor :as re])
  (:require [application.request-validator :as rv])
  (:require [application.transformer :as t])
  (:require [application.response-builder :as rb]))

(defn request-extract [request]
  (re/extract request))

(defn request-validate [request]
  (rv/validate request))

(defn request-transform [request]
  (t/transform request))

(defn response-build [request]
  (rb/build request))

이브: 그리고 사용하면 되죠.

(ns application.old-servlet
  (:use [application.facade]))

(defn service [request]
  (-> request
      (request-extract)
      (request-validate)
      (request-transform)
      (request-build)))

페드로: :use와 :require의 차이는 뭐죠?

이브: 거의 비슷해요, 하지만 :require는 이름공간을 매번 함수 앞에 붙여줘야 하는 반면, :use는 바로 그럴 필요 없이 바로 사용가능하죠.

페드로: 그러면, :use가 더 좋네요.

이브: 아뇨, :use는 조심해야 해요. 기존 이름공간에서 같은 이름을 사용하면 충돌이 날 수 있죠.

페드로: 오, 무슨 말인지 알겠어요. 어떤 이름공간에서 (:use [application.facade])를 호출할 때마다, 파사드에 있는 함수들 모두가 사용 가능하다?

이브: 네.

페드로: 아주 비슷하네요.


  • open/facade-pattern.txt
  • 마지막으로 수정됨: 2021/11/21 14:44
  • 저자 127.0.0.1