야, 너도 스토리플레이 서버개발자 할 수 있어
이세영업데이트:
스토리플레이 서비스 플로우를 알아보자
스플은 웹소설과 게임의 형태를 결합한 앱 서비스로 사용자의 선택에 따라 스토리가 진행되는 인터렉티브 채팅 형 서비스입니다. 스플의 에디터분들이 작품을 기획하고 작성하여, 작품을 출판하면 사용자가 작품을 재생할 수 있는 형태인데요. 대략의 플로우는 아래와 같습니다.
- 에디터는 작품의 스크립트를 작성합니다.
- 에디터가 작품 스크립트를 작성하여, 스플 어드민에서 파일로 작품을 등록합니다.
- 에디터가 스플 스튜디오에서 작품 스크립트를 작성합니다.
- 스플 서버는 앱에서 작품을 호출하면, 회차별로 내용을 내려줍니다.
- 앱은 내용를 읽어 들여, 정의된 동작에 맞게 효과나 본문을 사용자에게 보여줍니다. 사용자가 작품을 진행하는 중에 업적 획득, 선택 등의 이벤트가 발생하면, 앱은 서버와 통신하여 정보를 업데이트합니다.
서버 구성과 사용된 주요 기술들을 알아보자
Github Actions: Gibhub에서 제공하는 Workflow를 자동화하도록 도와주는 도구입니다. Github Actions를 이용하면, 테스트, 배포, 컨벤션 확인 등 다양한 작업을 자동화하여 처리를 할 수 있습니다. 스플 서버에는 어드민 배포를 위한 작업이 설정되어있습니다. 개발/운영 어드민 배포를 위해 각각의 특정 브랜치에 push를 하면, 어드민 코드를 배포하는 작업이 실행됩니다. 배포 진행 상황을 Github에서 바로 확인할 수 있어, 배포 실패에 대처하기 쉽습니다. 그리고 기존 배포 이력을 확인할 수 있어, 히스토리 추적도 가능합니다.
Bastion Host: Public 네트워크에서 Private 네트워크에 대한 액세스를 제공하기 위한 목적을 가진 서버입니다. Bastion 서버에 개발자의 SSH키를 등록하고, 보안 그룹에 개발자의 IP를 등록해 놓으면 개발자는 연결이 되어있는 Private 네트워크의 서버나 DB에 접근할 수 있습니다. 스플에서는 개발자의 로컬 환경에서 상용/개발 서버 접속이나 DB 데이터를 확인하려 할 때, Bastion 서버를 통해 접속하고 있습니다.
Airflow: Python 코드로 Workflow를 작성하고, 스케줄링, 모니터링하는 플랫폼입니다. DAG(Directed Acyclic Graph: Airflow에서의 Workflow, Task의 모음)에 각 Task간의 동작 순서와 실행 스케줄을 설정할 수 있습니다. 여러 클라우드 플랫폼에서 Airflow 관리환경을 제공하고 있는데, 스플은 AWS가 제공하는 MWAA에서 Airflow를 구동하고 있습니다. 주로 하루에 1회 특정 시간에 실행되는 배치성 작업을 설정해두고 있습니다. 구축 당시에는 서울 리젼은 아직 MWAA를 지원하지 않아, 도쿄 리젼에 구축하게 되었습니다.
GraphQL: 페이스북에서 만들어진 GraphQL은 웹 클라이언트가 데이터를 서버로부터 효율적으로 가져오기 위해 사용됩니다. API를 위한 쿼리 언어이자 서버 측 런타임으로 클라이언트에게 요청한 만큼의 데이터를 제공하는 데 우선순위를 둡니다. 그래서 앱 화면을 띄우기 위해 REST API는 여러 URL 요청이 필요하지만, GraphQL 단일 요청으로 필요한 리소스를 모두 받아올 수 있습니다. 이러한 장점을 바탕으로 스플 서버는 GraphQL을 이용하여 클라이언트와 통신을 하고 있습니다.
Apollo Server: GraphQL을 사용하는 모든 클라이언트와 호환되는 오픈소스 GraphQL서버입니다. API 명세서 역할을 할 수 있는 GraphQL playground(GraphQL introspection)를 제공합니다. 클라이언트 개발자는 GraphQL playground에 접속하여, 작업 이전에 쿼리를 먼저 호출해 볼 수 있습니다. 서버 개발자도 API가 정상적으로 호출이 되는지 체크를 해볼 수 있습니다. 하지만 GraphQL playground를 개발이든 운영이든 누구나 접근할 수 있다면, 보안상에 문제가 있을 겁니다. 그래서 스플은 Nginx HTTP Basic Authentication 설정을 통해, 외부의 접근을 차단했습니다.
Elastic Beanstalk: 서버에서 개발된 웹 애플리케이션 및 서비스를 간편하게 배포할 수 있는 서비스입니다. 필요한 경우 여러 가지 방법으로 플랫폼을 확장하여 옵션을 구성할 수 있습니다. 저희는 짧은 시간에 Best Practice로 API 서버를 구축 할 수 있는 수단이 Elastic Beanstalk(줄여서 EB)이라 생각하여 적용하였습니다. 스플 서버에서는 platform hooks를 통해서 서버가 빌드되는 초기에 서버 인프라 모니터링을 위한 New Relic Agent를 설치하고 Nginx config를 커스텀합니다. 그리고 개발/운영 각 환경에 맞는 환경변수와 인스턴스 정보들을 가져오기 때문에, 개발/운영 각각의 설정값 스크립트를 별도로 작성할 필요가 없어 편리합니다.
서버 코드 구조를 알아보자
storyplay-server
라는 repository를 받아보면, 크게 3개의 폴더를 확인할 수 있습니다.
scripter: 작품 스크립트를 서버에서 저장하는 데이터 형태로 파싱하는 로직이 있는 폴더입니다.
server: app/web/어드민에서 호출하는 API 코드가 작성된 폴더입니다. server 폴더 하위에는 여러 폴더 중에, .platform 폴더와 src 폴더의 하위 파일들을 살펴보겠습니다.
-
.platform: platform hooks는 Amazon Linux2에서만 지원합니다. 애플리케이션 소스 코드의 일부로 배포되는 스크립트와 실행 파일들을 하위에 포함하고 있습니다. 인스턴스의 프로비저닝 단계에서 EB에 의해 실행됩니다.
`-- .platform/ |-- nginx/ # Proxy configuration | `-- nginx.conf |-- hooks/ # Application deployment hooks | |-- prebuild/ | | |-- 00_install_nr_agent.sh | | |-- 01_create_htpasswd.sh | | `-- 02_download_credentials.sh
prebuild 폴더 하위의 파일은 EB 플랫폼 엔진이 애플리케이션 소스 번들을 다운로드하고 추출한 후 애플리케이션과 웹 서버를 설정하고 구성하기 전에 실행됩니다.
- 00_install_nr_agent.sh: 개발/운영 환경에 맞는 New Relic 라이선스 키를 받아와서, New Relic 설치하는 파일입니다.
- 01_create_htpasswd.sh: Nginx HTTP 인증을 위한 사용자 계정 정보가 있는 파일입니다.
아이디:패스워드
와 같은 구조로 패스워드는 MD5로 암호화가 되어있습니다. - 02_download_credentials.sh: 스플은 서버에서 필요한 서드파티 credential 정보를 s3에 저장해두었습니다. 이 셸에서 개발/운영에 업로드 되어있는 s3파일을 다운로드 받아, 서버에 복사합니다.
nginx 폴더에는 사용자 지정 프록시 구성 파일인 nginx.conf 파일이 있습니다. graphQL ground URL에 Nginx HTTP Basic Authentication 설정 내용도 nginx.conf에 작성되어 있습니다.
💡 platfrom hooks를 통해 실행되는 스크립트 내에서 get-config 도구를 이용해, EB에 설정한 환경 변수 값과 기타 인스턴스 정보를 찾아올 수 있습니다.
-
src: API와 해당하는 비즈니스 로직들이 작성된 폴더입니다. entity 단위로 모듈 폴더를 만들고 각 폴더는 아래와 같은 구조를 하고 있습니다.
`-- src/ |-- chapter/ | |-- entities/ | | `-- Chapter.ts | |-- common.ts | |-- input.ts | |-- logic.ts | |-- output.ts | `-- resolver.ts
chapter 폴더를 예로 들면, chapter라는 entity를 기준으로 폴더를 생성하고, entities라는 폴더 하위에 chapter entity를 정의합니다.
- entities/*.ts: 해당 모듈의 entity를 정의합니다. entity class에서 typeorm 컬럼 설정과 GraphQL 스키마 필드 정의를 합니다.
- common.ts: 모듈에서 공통으로 사용하는 객체들을 정의합니다. 주로 스키마 설명, type, enum을 선언하여 사용합니다.
- input.ts: resolver.ts에 선언된 함수들의 input 값을 선언해놓은 파일입니다. type-graphql로 각 input class마다 @InputType 데코레이터를 달고, 필드마다 @Field라는 데코레이터를 활용하여, 제약사항을 정의합니다.
- logic.ts: 주요 로직이 작성된 파일로서, DB 데이터 처리하는 부분 역시 작성되어있습니다. resolver.ts에서 호출 되거나, 다른 logic.ts 혹은 동일 파일내에서 호출하여 사용합니다.
- output.ts: resolver.ts에 선언된 함수들의 return 값을 선언해놓은 파일입니다. type-graphql로 각 output class마다 @ObjectType 데코레이터를 달고, 필드마다 @Field라는 데코레이터를 활용하여, 제약사항을 정의합니다.
- resolver.ts: 클라이언트가 호출하는 함수를 정의한 파일입니다. type-graphql의 @Resolver 데코레이터로 리졸버임을 클래스 상단에 정의해줍니다. 정의된 함수는 간단한 로직이면, DB 데이터를 직접 처리하거나, logic.ts을 호출하여 결괏값을 받습니다. @FieldResolver라는 데코레이터를 달고 있는 필드는 @Root로 entity 자기 자신을 인자로 받아서, 필드 값을 설정할 수 있습니다.
site: 어드민 화면 코드가 작성된 폴더입니다. react 기반으로 커스텀된 component를 이용하여, 어드민 메뉴들을 구현하였습니다.
배포 전략을 알아보자
스토리플레이 서버에는 master
브랜치만 존재하며, default 브랜치입니다.
- 스크럼 방식으로 개발을 진행하고 있어서, 스프린트 단위로 feature 브랜치를 생성하여 작업을 진행합니다. 스프린트 feature 브랜치는 master에서 브랜치를 생성하고, 스프린트 하위의 작업들은 스프린트 feature 브랜치에서 작업 티켓 이름(feat_SPAPP-***)으로 브랜치를 생성하여 작업을 합니다.
- Pull Request는 작업 티켓 단위로 요청하고, 머지 브랜치를 스프린트 feature 브랜치로 설정합니다. Review는 되도록 당일 완료합니다.
- 스프린트 feature 브랜치를 개발에 올리고, QA중 나오는 bug fix는 스프린트 feature 브랜치에 바로 머지합니다.
- QA가 완료되면, 스프린트 feature 브랜치는 master 브랜치에 squashed merge를 합니다.
- 운영에 배포하기 전에 master 브랜치에서 테스트 케이스를 실행하고, 이상이 없다면 상용배포를 진행합니다. 배포가 완료되면, Tag를 릴리즈합니다.
- hotfix가 발생하면, master에 직접 push를 하되, commit 메시지에 (fix) 라는 내용을 덧붙여줍니다. 스프린트 feature 브랜치에도 rebase로 적용하거나, 협업하는 브랜치라면 cherry-pick하여 적용합니다.
💡 만약, feature 브랜치에 commit된 내용 중 일부 commit이 master에 배포가 되어야한다면 어떻게 해야할까요?
cherry-pick을 이용해봅시다. git의 cherry pick은 다른 브랜치에서 특정 커밋만을 내가 작업하고 있는 브랜치에 반영하고 싶을 때 사용할 수 있는 기능입니다. 현재는 적은 수의 개발자가 협업을 하다 보니, 몇몇 시행착오를 거쳐 위와 같이 정의를 했는데요. 배포 전략은 다음에도 협의를 통해 바꿔나갈 수 있습니다.
앞으로 같이 개선해보자
지난 1년 동안 여러 개발자분들의 수고로 사용자에게 선보일 수 있는 서비스가 되었습니다. 하지만 피쳐 개발에 치우쳐 서버 관점에서 개선이 필요한 것들이 아직 많습니다. 몇 가지 적어보자면 아래와 같습니다.
- 로그 시스템 구축: 스플 서버는 아직 로그 시스템이 구축되어 있지 않습니다. 운영상에서 이슈가 발생했을 때, 증상 확인 및 히스토리 추적이 어려운데요. New Relic logs를 연동하면 New Relic Infra Metric과 연계하여 운영 이슈 분석에 도움이 될 것 같습니다.
- 배포 자동화: Elastic Beanstalk를 활용하여, 서버 배포를 진행하고 있습니다. 하지만 개발자의 로컬 환경에서
eb deploy ...
와 같은 명령어를 입력하여 배포를 진행하는게 배포를 진행하는 중에도 휴먼에러를 일으키지는 않을까 불안했습니다. 명령어를 잘못 입력 하여 발생할 수 있는 에러는 없애보고자, 최소 스크립트 셸을 작성해두거나, Github Actions을 이용한 배포 자동화로 개선해볼 수 있을 것 같습니다. - 테스트 케이스 로딩 시간 단축: 서버는 운영 배포를 진행하기 이전에 전체 테스트 케이스를 실행합니다. 이번에 추가된 피쳐가 기존의 다른 기능에 이펙트가 있는지 확인하기 위함인데요. 이 테스트 케이스를 완료하는데 30분 정도가 걸립니다. 앞으로 피쳐가 늘어날 수록 테스트 케이스도 늘어날 것이고, 그러면 소요 시간은 더 증가할 것입니다.
이외에도 해야 하는 것들과 해볼 수 있는 것들이 무궁무진한 스플서버입니다. 아직은 적은 인원으로 운영을 진행하고 있지만, 서비스가 더 잘되어서 앞으로 많은 분과 함께할 수 있게 되면 좋겠습니다.☺️
[참고 문서]
GraphQL이란? API 관리, 설계, 사례, 통합, 배포 가이드 및 단점
댓글남기기