1 of 61

차세대 클라우드 웹 서비스�보일러 플레이트 개발기

차세대 클라우드 팀 백승주(Becker)

2 of 61

오늘 이야기할 내용

0. 차세대 클라우드 파트에서 하고 있는 일

1. 보일러 플레이트 프로젝트를 시작한 이유

2. 단일 책임을 지키게 하기 위한 노력들

3. 미들웨어를 활용한 횡단 관심사 분리

4. DB Session의 생명주기를 관리하는 코드를 서비스코드 외부에서 관리하는 방법

3 of 61

0. 차세대 클라우드에서 하고 있는 일은?

오픈스택 감싸서 G클라우드 만들기

4 of 61

오픈스택을 통해 G클라우드를 만드는 팀

  • 차세대 클라우드 팀에서는 내년에 오픈예정인 오픈스택 기반의 클라우드 서비스를 개발중

  • 오픈스택이란 클라우드 자원을 생성하고 관리하는 IaaS 플랫폼

5 of 61

오픈스택을 통해 G클라우드를 만드는 팀

  • 주 개발 언어는 Python, 웹 서비스를 만들기 위해 FastAPI라는 라이브러리를 사용 중

  • MSA 서비스를 지향해서 서비스들이 독립적으로 개발되고 운영중

6 of 61

차세대 클라우드 서비스 아키텍처

7 of 61

1. 보일러 플레이트 프로젝트를 하는 이유?

차세대 클라우드는 춘추전국시대

8 of 61

차세대 클라우드 현황

  • FastAPI를 사용해서 프로젝트 진행한지 1년 반정도 된 상황
  • 형상관리가 매우 힘든 코드
  • 프로젝트, 개발자마다 다른 구조의 어플리케이션
  • 단일 책임 원칙을 지키지 못하는 함수, 클래스
  • 횡단 관심사가 분리되지 못한 서비스 코드
  • MSA스럽지 않은 MSA 아키텍처 구조

9 of 61

차세대 클라우드 현황

  • FastAPI를 사용해서 프로젝트 진행한지 1년 반정도 된 상황
  • 형상관리가 매우 힘든 코드
  • 프로젝트, 개발자마다 다른 구조의 어플리케이션
  • 단일 책임 원칙을 지키지 못하는 함수, 클래스
  • 횡단 관심사가 분리되지 못한 서비스 코드
  • MSA스럽지 않은 MSA 아키텍처 구조

10 of 61

보일러 플레이트가 필요하다

  • 의견 : 프레임워크 적용하자!

  • 현실 : 프레임워크를 적용하기에는 너무 많은 부분을 변경해야 한다.
  • 그럴거면 프로젝트를 다시 시작하는 편이 빠르다.

  • 타협 : 최소한의 변경으로 레거시 코드를 개선할 수 있는 방법을 제시하는 템플릿을 만들자!

11 of 61

보일러 플레이트 프로젝트를 통해 기대하는 점

  • 코드 형상을 강제 X

  • 레퍼런스 용도의 역할

  • 단순화, 규격화, 재사용 할 수 있는 방식을 참고

12 of 61

보일러 플레이트 프로젝트를 통해 기대하는 점

  • 코드 형상을 강제 X

  • 레퍼런스 용도의 역할

  • 단순화, 규격화, 재사용 할 수 있는 방식을 참고

로직을 작성하며 마주할 수 있는 기술적인 상황에 대해 공유하고 참고할 수 있는 코드베이스를 만드는 것이 목표

13 of 61

2. 단일 책임을 지키게 하기 위한 노력

의존성 분리/주입 방식을 어떻게 구현했는지

14 of 61

단일 책임이 지켜지기 힘든 상황

  • FastAPI는 하나의 함수로 레이어를 나누지 않고 구현되는 레퍼런스가 많이 있다.

  • Django처럼 Model, View, Template 과 같은 계층 구조를 강제하지 않음

  • 하나의 Router 함수 내에서 모든 기능을 구현할 경우 ‘단일 책임 원칙’이 무너짐

  • Layered 아키텍처로 레이어 간 관심사가 적절하게 분리되지 않음

  • 의존성 주입 메커니즘에 의존하는 각 레이어 클래스

15 of 61

단일 책임이 지켜지기 힘든 상황

  • FastAPI는 하나의 함수로 레이어를 나누지 않고 구현되는 레퍼런스가 많이 있다.

  • Django처럼 Model, View, Template 과 같은 계층 구조를 강제하지 않음

  • 하나의 Router 함수 내에서 모든 기능을 구현할 경우 ‘단일 책임 원칙’이 무너짐

  • Layered 아키텍처로 레이어 간 관심사가 적절하게 분리되지 않음

  • 의존성 주입 메커니즘에 의존하는 각 레이어 클래스

16 of 61

단일 책임을 지키게 하기 위한 노력

  • 레이어 역할을 보다 명확하게 구분

  • 순수한 클래스를 유지해 FastAPI 에서 제공하는 의존성 주입 메커니즘에 의존하지 않게 만들기

  • 의존성을 하나의 외부 소스에서 관리하는 방식으로 변경

  • Dependency Injector 적용

보일러 플레이트에서 하고싶은 내용

17 of 61

단일 책임이 지켜지기 힘든 상황

이전 Layered 구조

Router

Service

Repository

OpenStack Client

OpenStack

Database

18 of 61

단일 책임이 지켜지기 힘든 상황

이전 Layered 구조

Router

Service

Repository

OpenStack Client

OpenStack

Database

19 of 61

단일 책임이 지켜지기 힘든 상황

이전 Layered 구조

Router

Service

Repository

OpenStack Client

OpenStack

Database

20 of 61

단일 책임이 지켜지기 힘든 상황

이전 Layered 구조

Router

Service

Repository

OpenStack Client

OpenStack

Database

21 of 61

단일 책임이 지켜지기 힘든 상황

이전 Layered 구조

Router

Service

Repository

OpenStack Client

OpenStack

Database

22 of 61

단일 책임이 지켜지기 힘든 상황

이전 Layered 구조

Router

Service

Repository

OpenStack Client

OpenStack

Database

23 of 61

단일 책임이 지켜지기 힘든 상황

보일러 플레이트 제안 Layered 구조

Router

Facade

Repository

OpenStack Client

OpenStack

Database

Validation

Service

Service

24 of 61

단일 책임이 지켜지기 힘든 상황

보일러 플레이트 제안 Layered 구조

Router

Facade

Repository

OpenStack Client

OpenStack

Database

Validation

Service

Service

25 of 61

단일 책임이 지켜지기 힘든 상황

보일러 플레이트 제안 Layered 구조

Router

Facade

Repository

OpenStack Client

OpenStack

Database

Validation

Service

Service

26 of 61

단일 책임이 지켜지기 힘든 상황

보일러 플레이트 제안 Layered 구조

Router

Facade

Repository

OpenStack Client

OpenStack

Database

Validation

Service

Service

27 of 61

레이어 분리 적용 예시

28 of 61

단일 책임을 지키게 하기 위한 노력

의존성 주입 메커니즘에 의존하는 클래스

29 of 61

단일 책임을 지키게 하기 위한 노력

의존성 주입 메커니즘에 의존하는 클래스

30 of 61

단일 책임을 지키게 하기 위한 노력

의존성 주입 메커니즘에 의존하는 클래스

31 of 61

단일 책임을 지키게 하기 위한 노력

의존관계를 파악하기 힘들다

Dependency.py

32 of 61

단일 책임을 지키게 하기 위한 노력

의존관계를 파악하기 힘들다

Dependency.py

33 of 61

단일 책임을 지키게 하기 위한 노력

의존관계를 파악하기 힘들다

Dependency.py

34 of 61

단일 책임을 지키게 하기 위한 노력

Dependency.py

의존관계를 파악하기 힘들다

  • 의존성을 한눈에 파악하기 힘들다.
  • 의존성 주입을 어떻게 할 것인지에 대해 클래스에 계속 명시해야한다.

순수한 Python Class를 만들지 못함…

모든 클래스에 Depends 명시

Depends

Depends

Depends...

35 of 61

단일 책임을 지키게 하기 위한 노력

Dependency Injector 를 사용해 의존성 관리하기

  • Python에서 사용되는 DI 라이브러리를 사용
  • 의존성을 클래스 외부에서 주입 가능
  • 의존성을 명시적으로 관리할 수 있는 Container 사용

dependency.py -> container.py 로 변경

  • 의존 객체를 주입하는 패턴을 선택가능

Factory, Singleton, Object, Callable …

36 of 61

단일 책임을 지키게 하기 위한 노력

Dependency Injector 도입 - Container

37 of 61

단일 책임을 지키게 하기 위한 노력

Dependency Injector 도입 - Container

38 of 61

단일 책임을 지키게 하기 위한 노력

Dependency Injector 도입 - Container

39 of 61

단일 책임을 지키게 하기 위한 노력

Dependency Injector 도입 – 의존성 외부 주입

의존성 주입 관련된 코드가 제거됨

40 of 61

단일 책임을 지키게 하기 위한 노력

Dependency Injector 도입 – 의존성 외부 주입

Router 함수 에서만 의존성 주입 관련 코드가 존재

41 of 61

3. 미들웨어로 횡단 관심사 분리

모든 서비스 함수에 적용된 코드를 미들웨어로 보내려면?

42 of 61

서비스 API 횡단 관심사

차세대 클라우드 횡단 관심사

43 of 61

서비스 API 횡단 관심사

차세대 클라우드 횡단 관심사

인증 관련 체크 로직, 이벤트 로그 관련 로직이 모든 코드에 적용

44 of 61

미들웨어로 분리되지 않은 코드

인가 관련 코드

45 of 61

미들웨어로 분리되지 않은 코드

인가 관련 코드

46 of 61

미들웨어로 분리되지 않은 코드

인가 관련 코드

47 of 61

미들웨어로 분리되지 않은 코드

이벤트 로그 관련 코드

- 네트워크 생성 시

- 네트워크 수정 시

- 네트워크 삭제 시

48 of 61

미들웨어로 분리되지 않은 코드

Service 레이어에서 횡단 관심사를 분리하기 위해 적용했던 시도

  • Router 레이어로 옮기기
  • 함수로 만들어서 최대한 영향범위 작게 만들기
  • 데코레이터 활용 (ex: @permission_required) <- 현재 프로젝트에 적용되어 있는 방식

미들웨어와 Context Var를 사용한다면???

49 of 61

미들웨어로 분리되지 않은 코드

미들웨어와 컨텍스트 변수(Context Var) 적용 방안

  • ContextVar로 정의된 변수는 스레드, 이벤트 루프, 동일한 요청 경로 내에서 독립적인 변수 공간을 가짐

  • 웹 어플리케이션을 기준으로 A요청과 B요청이 있다고 할 때, A와 B가 동일한 변수로 서로 다른 값을 가질 수 있다

50 of 61

미들웨어로 분리되지 않은 코드

미들웨어와 컨텍스트 변수(Context Var) 적용 방안

  • ContextVar로 정의된 변수는 스레드, 이벤트 루프, 동일한 요청 경로 내에서 독립적인 변수 공간을 가짐

  • 웹 어플리케이션을 기준으로 A요청과 B요청이 있다고 할 때, A와 B가 동일한 변수로 서로 다른 값을 가질 수 있다

미들웨어에서 Context Var에 값을 저장/불러오는 방식을 사용해서 서비스 로직에서 횡단 관심사를 분리할 수 있다!

51 of 61

미들웨어로 분리되지 않은 코드

미들웨어 적용 방안

XRequestIdMiddleware

요청 식별자 채번

52 of 61

미들웨어로 분리되지 않은 코드

미들웨어 적용 방안

EventLogMiddleware

이벤트 로그를 발신하는 미들웨어

53 of 61

미들웨어로 분리되지 않은 코드

미들웨어 적용 예시

비지니스 로직

54 of 61

미들웨어로 분리되지 않은 코드

미들웨어 적용 예시

비지니스 로직

55 of 61

미들웨어로 분리되지 않은 코드

미들웨어 적용 예시

비지니스 로직

56 of 61

미들웨어로 분리되지 않은 코드

미들웨어 적용 예시

비지니스 로직

57 of 61

미들웨어로 분리되지 않은 코드

미들웨어 적용 예시

비지니스 로직

58 of 61

보일러 플레이트를 만들면서 들었던 생각들

프레임워크에 대한 필요성?

-> 처음 : 프레임워크가 없는 것에 대한. 아쉬움

-> 지금 : 우리가 만들어가는 느낌, 맨땅에서 배우는 것도 많음

처음 프로젝트를 시작할 때 구조 설계에 충분한 시간이 필요

-> 난개발이 될 수 밖에 없음

-> 설계에 많은 시간이 필요한게 아님

-> 나중에 유지보수하는데 더 많은 비용이 든다고 생각

개선 아이디어를 계속 적용해봐야함

-> 중간 중간 개선 시도가 없었으면, 시도 조차 못해봤을 내용들

-> 팀원들과 소통하고 아이디어를 적용하면서 반영

59 of 61

못다한 이야기…

  • DB Session을 서비스 로직 외부에서 관리하는 방법

  • Fixture, Mock 을 사용해서 쉽게 단위테스트 코드를 작성하는 예시

  • 공통 기능을 분리한 cloud-util 프로젝트

  • Async를 포기하고 Sync한 방식으로 돌아가야하는 이유

60 of 61

질문

다른 질문 있으면!🙋‍♂️

61 of 61