공부/Spring-Sagan

[spring-sagan]어디로부터 가져오느냐 그것이 문제로다

aeomhs 2020. 2. 11. 00:42

개요

Spring sagan 애플리케이션을 어떻게 하면 더 깔끔한 구조로 사용자화할 수 있을까?

sagan 애플리케이션은 기본적으로 자신들의 Github Repository와 강하게 결합되어있다.

문서와는 약간 다른 상황이다. `application.yml`에서 단 3줄만 더 추가하면 모든게 해결될 것 같았지만, 아니었다.

소스 코드를 직접 이렇게 저렇게 수정하면서 내 Github Repository로부터 가이드를 가져오도록 구현했다.

 

그리고 지금 내가 원하는 결과는 얻어냈지만, 그 과정과 내부가 마음에 들지 않는다.

가장 마음에 들지 않는 것은 Exception을 활용한 분기 제어이다.

모든 것은 Github `spring-guides`Organization과의 의존 관계에서 시작되는 것 같다.

 

이 글은 의존 관계를 끊고자 노력한 것들과, 

지금 헤어나오지 못하는 딜레마에 대해서 기록하는 것을 목표로 한다.

글을 쓰고 어느 정도 해결됐다.

 

과정

 

# 요구 사항 명세

내가 추가하고자 하는 기능은 `sagan-renderer/.../resource/application.yml` 설정 파일에 아래와 같은 내용을 추가하면

나의 Github 저장소에 존재하는 `gs-`로 시작하는 Repository를 모두 보여주는 것이다.

...  
  guides:
    owner:
      name: ${GITHUB_GUIDES_OWNER_NAME:aeomhs}
      type: ${GITHUB_GUIDES_OWNER_TYPE:users}

 

 

# 첫 번째 수정

이와 관련된 `RendererProperties.java`에 해당 Property를 모두 추가했다.

그리고 조금 더럽지만, 일단 내가 원하는 결과가 나오도록 수정했다.

 

그리고 이 과정에서 `sagan-renderer` 애플리케이션의 구조를 이해하는데 도움이 많이 되었다.

핵심적인 클래스는 크게 3가지가 있다.

`/guides/..` 요청이 수행되는 과정 요약, 주요 클래스

`GuidesController` : 8081 포트를 통해 sagan-site 애플리케이션으로부터 요청을 받으면 가이드를 렌더링한 결과를 반환(응답)한다.

                                 1. `/guides/` - 모든 가이드 리스트에 대한 정보를 반환한다.  (fetch)

                                 2. `/guides/{type}/{guide}` - 해당하는 가이드에 대한 정보를 반환한다.  (fetch)

                                 2. `/guides/{type}/{guide}/content` - 해당하는 가이드의 렌더링 결과를 반환한다.  (download)

`GithubClient` : spring-guides organization Github 저장소로부터 Repository Fetch 혹은 downloadAsZipball 기능을 담당한다.

`GuideRenderer` : spring-guides organization Github 저장소로부터 해당하는 guide content를 렌더링한다.

 

화면에 보이는 결과는 내가 원하는 바였지만,

그 뒤에는 중복이라는 악마와의 거래가 있었다.

 

 

# 두 번째 수정

더 높은 단계의 수정이 필요해보였다. 가장 중복이 심한 `GithubClient`부터 손 볼 필요가 있었다.

이 클래스에는 주요 3가지 기능의 메서드가 존재했다.

`downloadRepositoryAsZipball`

`fetchOrgRepositories`

`fetchOrgRepository`

그런데 이름에서부터 Organization과의 관계가 눈에 띈다. 여기서 든 생각이 있다.

이 사람들이 user repository에 대한 가이드 렌더링 기능을 제공할 생각이 있긴 했던 것일까?

 

위의 기능 3개를 `Client` 인터페이스로 넘기고,

`GithubClient`는 Github REST API와 관련된 기본(공통) 기능만 구현하는 추상 클래스로

`OrgGithubClient`, `UserGithubClient`를 위 2개의 타입을 확장하는 클래스로 수정했다.

그리고 관련된 `Controller, Renderer` 클래스도 수정했다.

 

그런데 결과가 그렇게 마음에 들지 않았다.

`Client`라는 인터페이스 타입으로 organization과 user 저장소에 대한 다형성을 제공하는 것이 목표였는데,

애초에 어디서 가져올 것인지에 대한 정책이 없었다. 무조건 organization으로부터 먼저 가져오는 것이 현 상황이다.

그래서 Controller와 Renderer는 client를 두 개나 손에 쥐고 있는 상황이 되어버렸다.

그리고 무엇보다 아직 중복이 있다.

 

 

# 딜레마

무엇이 문제일까? 조금 더 높은 차원?

우선 `/guides/`에 맵핑된 `listGuides` 메서드의 경우 큰 문제는 없었다. (Client가 두 개라는 것 빼고)

어쨌든 둘 다 가져오면 되기 때문이다. 또, `userName` property가 없어도,

즉 사용자에 대한 설정이 존재하지 않아도 문제가 되지 않는다.

문제는 특정한 가이드에 대한 요청이다. Fetch를 하던, DownloadAsZipball을 하던, 

Organization과 User Repository 중 어디로부터 가져와야하는지 알 수가 없다.

 

요구 사항 명세가 잘 되지 않은 것 같아서 생각해보았다.

  1. Organization으로부터 시도한 후, 없을 경우 User로부터 가져온다.

  2. 애초에 Organization 혹은 User 둘 중 하나만 지원한다.

  3. `{type}`에 User임을 확인할 수 있는 인자를 넣는다. (현재는 "gs-" : "Getting Started")

 

우선 지금은 1번을 만족하고 있다. (그리고 2번째 커밋 결과이다.)

문제는 예외 처리를 분기로 사용할 뿐이다. 마음에 들지 않는다.

Client에서 Fetch 혹은 Download를 실패하면 Exception을 던진다.

 

2번은 그냥 요구사항이 내 마음에 들지 않는다. 욕심이다.

나는 로컬 환경에서 둘 다 보고 싶다.

최후의 수단으로 생각중이다.

 

3번은 가장 깔끔해보인다. 

`GuideType`에 번역본에 대한 타입만 추가되면,

사용자 저장소로부터 가져오는 가이드는 전부 독립적으로 구현될 것 같다.

사실 그래서 이 방법을 2번째 수정 단계에서 시도했었는데

`sagan-site` 애플리케이션도 같은 `GuideType`을 정의하고 있고,

또 이를 활용해서 렌더링 요청을 하고 있다.

꽤 대대적인 수정이 필요할 것 같아서 우선 보류했다.

 

 

# RenderScope

그리고 오늘은 `application.yml`을 통해 `renderScope`이라는 설정을 도입해보았다.

renderScope:
  - ${RENDER_SCOPE_ORG:true}
  - ${RENDER_SCOPE_USER:true}

그리고 Masking을 통해 현재 RenderScope에 대한 정보를 확인하고 필요한 Client를 호출하는 방식이다.

하지만 이것도 결국 "어디로부터 가져오느냐"에 대한 문제를 해결해주지는 않았다.

 

 

결론

가이드에 대한 Repositry를 어디에서 가지고 오느냐에 대한 정책 문제인 것 같다.

지금까지 정리한 적은 없고, 머릿속에서만 이상향을 생각하면서 접근했던 것 같다.

 

글을 쓰다보니, 내가 원하는 목적을 이루기 위해서는

`GuideType`을 건드려 볼 필요도 있겠다는 생각이 든다.

 

정리해보길 잘했다. 한 번 해봐야겠다.

 

 

참고 자료

1. Spring. spring-guides/getting-started. https://github.com/spring-guides/getting-started-guides/wiki