세상에 나쁜 코드는 없다

Web IDE Backend Architecture 본문

웹개발/백엔드

Web IDE Backend Architecture

Beomseok Seo 2023. 12. 24. 13:02

개요

구름톤 트레이닝에서 12월 약 한달 간 수행했던 프로젝트인 "ChatGPT와 함께하는 Web IDE" 프로젝트를 소개합니다.

저는 백엔드 4명, 프론트엔드 3명으로 구성된 팀에서 백엔드 개발자이자 팀장으로서 프로젝트를 리드했습니다.

https://github.com/The-Great-Sign/IDE-Project-backend

주요 기능

기본적으로는 Goorm IDE의 기능을 모방하되, ChatGPT에 의한 편의기능을 부가적으로 제공하도록 했습니다.

  • 소셜 로그인 : 소셜 로그인을 통해 사용자는 번거로운 회원가입 / 로그인 과정을 거치지 않고 서비스를 이용할 수 있습니다.
  • 프로젝트 단위의 개발 환경 제공 : 사용자는 여러 프로젝트를 만들고, 소스코드를 작성하며 실행시킬 수 있습니다. 모든 내용은 저장되며, 다른 사용자를 초대해 함께 소스코드를 수정할 수 있습니다.
  • 다양한 언어 지원 (Python, Java, CPP) : 프로젝트 생성 시점에 사용할 언어를 선택할 수 있습니다.
  • 채팅 : IDE의 프로젝트에 참여해있는 사용자간에 텍스트를 주고 받을 수 있는 기능입니다. 사용자의 입/퇴장시에 '@@@님이 입장 | 퇴장 했습니다.' 라는 문구가 보여집니다.
  • 다중 사용자 실시간 편집 : 여러 사용자가 동시에 하나의 파일에 접근하여 소스코드를 수정할 수 있습니다. 이 내용들은 실시간으로 다른 사용자에게 보여집니다.
  • 터미널 : 터미널을 통해 프로젝트 파일에 대한 조작을 할 수 있으며 프로그램을 수행시킬 수 있습니다.
  • ChatGPT를 통한 코드 리뷰 및 질문 : 현재 작성중인 소스코드에 대한 코드 리뷰를 간편하게 받을 수 있으며, 질문이 있는 경우 내장되어있는 ChatGPT 기능을 통해 결과를 받아 볼 수 있습니다.

아키텍처

도커 컨테이너를 통한 컴파일과 실행

Web IDE를 구현하기 위해서는 사용자가 작성한 소스코드를 컴파일 한 뒤 실행시켜서 그 결과를 사용자에게 건네줘야한다는 요구사항이 있습니다.

컴파일

사용자로부터 받은 소스코드를 컴파일하는 방법은 여러가지가 있습니다.

Java에서 지원하는 javax.tools.JavacCompiler를 사용한다면 사용자가 작성한 코드를 컴파일하여 class 파일을 생성할 수 있습니다. 하지만 이 방법으로는 Java 코드만을 컴파일 할 수 있습니다. 다양한 언어를 지원하려면 비슷한 기능을 제공하는 서드 파티 라이브러리를 찾아서 적용시켜야 했습니다. 한 달이라는 제한된 시간에 이 방법을 적용하기에는 어려워 보였습니다.

다른 방법으로는 미리 서버에 여러 언어 환경을 구성해놓고, java.lang.ProcessBuilder를 사용하여 컴파일을 수행할 수 있는 명령어를 수행하는 프로세스를 생성하는 방법을 생각해봤습니다. 하지만 이 방법을 적용하기 위해서는 언어 지원이 추가될 때마다 언어 환경을 구성해야하고, 하나의 언어 내에서도 다양한 버전의 언어를 설치해서 운영해야한다는 번거로움이 있었습니다.

따라서 선택한 방법은 도커 컨테이너를 사용한 방법입니다. 도커 컨테이너에 컴파일 명령을 보내서 컴파일을 수행하면 비교적 간단하게 수행할 수 있으며, 도커 허브의 이미지를 사용하여 다양한 언어를 지원하는 것이 가능해집니다.

실행

도커 컨테이너를 사용한다면 사용자 코드 실행할 때 발생하는 문제를 줄일 수 있습니다. 만약 단순하게 ProcessBuilder를 통해 컴파일을 수행하고 해당 코드를 실행하는 방향으로 간다면, 사용자의 악의적 의도로 코드를 작성했을 때에 그 코드가 실행 서버에서 그대로 돌아가게 됩니다. 도커 컨테이너에서 사용자 코드를 실행시킨다면, 이는 실행 서버와 격리된 공간에서 수행되므로 악의적 코드의 문제가 격리된 환경 내에서만 존재하게 됩니다.

터미널 기능을 제공하려면

사용자가 터미널을 통해 프로젝트 디렉터리를 관리하고 코드를 수행할 수 있으려면 사용자의 터미널 명령어를 처리할 수 있는 무언가가 필요합니다. 저희 팀은 이러한 기능을 구현하기 위해 도커 컨테이너를 적극적으로 사용하고자 했습니다.

사용자가 IDE를 통해 프로젝트에 접근하는 과정에서 해당 프로젝트의 언어에 맞는 도커 컨테이너가 실행됩니다. 이 도커 컨테이너는 사용자의 소스코드 디렉터리 영역을 볼륨으로 가지고 있습니다. 사용자가 애플리케이션 서버로 명령어를 보내면, 애플리케이션 서버는 이를 알맞은 컨테이너에 전달하여 컨테이너가 직접 명령어를 수행하게 합니다. 이후 도커 컨테이너가 수행한 결과는 다시 애플리케이션 서버를 거쳐 사용자 측으로 전달됩니다.

이러한 방법을 사용한다면 사용자가 어떠한 터미널 명령어를 보낸다 하더라도 도커 컨테이너의 격리된 환경으로 인해 다른 컨테이너 혹은 다른 프로젝트의 소스코드와 간섭되는 일을 막을 수 있습니다. 또한 볼륨 마운팅을 통해 사용자는 자신이 작성한 디렉터리 구조를 cd 명령어로 자유롭게 이동할 수 있으며, 컴파일 및 실행 명령어를 통해 그 결과를 확인할 수 있습니다.

ECS

위와같은 구조에서는 프로젝트 단위로 도커 컨테이너가 하나씩 띄워집니다. 따라서 컨테이너의 개수를 사용자의 요청에 따라 늘리고 줄이며, 생명주기를 관리하는 방식을 고안해야 합니다.

이를 구현하기 위해서 저희는 Amazon ECS를 사용하기로 결정했습니다. Amazon Elastic Container Service는 컨테이너를 효율적으로 배포하고 규모를 조정하는데 특화되어있는 컨테이너 오케스트레이션 서비스입니다. ECS 사용자가 IDE에 접근할 때마다 프로젝트의 디렉터리를 볼륨으로 하는 컨테이너를 생성하고 사용자의 동작들을 수행하게 만들었습니다.

ECS는 시작 유형으로 Fargate, EC2, External 중 선택할 수 있습니다. 저희는 각 컨테이너가 별도의 운영체제 없이 Serverless Container로만 동작해도 요구사항을 만족시킬 수 있으므로 Fargate 유형을 채택하기로 했습니다.

EFS

사용자가 작성한 파일을 어떻게 저장하고 관리해야할지도 고민이었습니다. 다음과 같은 저장 방법들에 대해 고민하였고 최종적으로는 EFS를 사용하기로 결정했습니다.

  • 애플리케이션 서버의 파일 시스템에 저장 : 사용자가 작성한 코드를 애플리케이션 서버에 파일 형태로 저장하는 것도 유효한 방법이라는 생각이 들었습니다. 하지만 이 경우 규모 확장이 되고 스케일 아웃을 해야하는 경우 적절치 못하다는 생각이 들었습니다.
  • RDB, S3에 저장 : 애플리케이션 서버에서 저장 공간을 떼어낸다는 점에서는 의미가 있어 보였습니다. 하지만 결국 사용자의 코드를 수행하기 위해서는 디렉터리 구조를 갖는 파일 시스템으로 변환하는 로직이 필요할 것으로 보였습니다. 따라서 RDB나 S3에 저장하는 것은 추가적인 비용을 가져올 것으로 파악했습니다.
  • EFS에 저장 : Amazon Elastic File System은 공유 파일 시스템을 지원하는 아마존의 서비스입니다. 이는 마운트 방식으로 동작하므로 도커 컨테이너와 함께 사용하기에도 효과적이었습니다. EFS에는 파일 시스템의 특정 위치를 마운트할 수 있는 Access Point 라는 것을 제공하는데, 이것을 통해 각각의 컨테이너가 서로 분리된 파일 영역을 볼륨으로 가져갈 수 있었습니다.

컨테이너의 실행 출력

컨테이너에서 프로그램을 실행시키면 그 결과 출력은 컨테이너에서 반환됩니다. 따라서 이 출력 결과를 애플리케이션 서버가 알아차릴 수 있는 방법을 고안해야했습니다.

첫번째로 구현했던 방법은 CloudWatch와 Lambda를 사용해서 로그를 애플리케이션 서버로 보내는 방법입니다. CloudWatch에 로그 생성 이벤트가 발생하면 Lambda가 수행되어 애플리케이션에 로그를 전송합니다. 하지만 이 방법은 너무 많은 서비스를 거쳐야해서 명령어에 대한 결과를 받는데에 약 5초 정도가 걸렸습니다.

이후 방법을 수정하여서 도커 컨테이너에서 curl을 수행하여 로그를 직접 애플리케이션 서버로 보내는 방법으로 바꾸었습니다. 이를 통해 컨테이너 출력 결과를 받는 응답시간이 매우 단축되었습니다.

구현 포인트

프로젝트

저희가 다루는 '프로젝트'라는 개념은 사용자의 작업 단위이며 파일 디렉터리, 컨테이너 이미지를 포함합니다

프로젝트 생성

사용자가 프로젝트를 생성하는 시점에 AWS의 자원들이 만들어집니다.

첫번째 자원은 EFS AccessPoint 입니다. EFS AccessPoint는 EFS 저장공간의 특정 디렉터리를 바라보는 포인터의 역할을 제공하며, AccessPoint가 컨테이너 이미지 생성시 볼륨 마운트의 기준이 됩니다. Access Point를 만들고 운영하기 위해서는 해당 디렉터리가 실제로 만들어져있어야 하기 때문에, AccessPoint를 만들기 전에 EFS 디렉터리에서 해당 프로젝트가 사용할 디렉터리 공간을 만들고, 최초 프로젝트 생성 시 넣어줄 기본 파일들을 생성하게 됩니다.

두번째 자원은 ECS의 TaskDefinition입니다. TaskDefinition은 하나의 ECS 인스턴스의 청사진이며 여러개의 컨테이너 이미지를 포함할 수 있습니다. 프로젝트 생성 시점에 사용자로부터 받은 언어정보에서 가져온 이미지, AccessPoint 등을 조합하여 TaskDefinition을 만들어야 합니다.

AccesPoint와 TaskDefinition의 식별자는 이후 RDB에 프로젝트 메타 데이터와 함께 저장되어, 컨테이너를 실행시킬때에 사용됩니다.

프로젝트와 컨테이너

프로젝트와 해당 프로젝트의 컨테이너는 1대1 매칭이 되는 개념이지만, 두 개체의 생명주기는 다릅니다. 사용자가 IDE를 사용하지 않는다면 컨테이너는 실행될 필요가 없습니다. 따라서 효과적인 과금관리를 하려면 사용자가 IDE를 통해 프로젝트에 접근할 때에만 해당 컨테이너가 실행되게 만들고, 어떤 사용자도 작업중이지 않은 경우 컨테이너를 종료해야합니다.

프로젝트 입장 로직

프로젝트 입장 로직은 다음과 같습니다.

프론트엔드 애플리케이션은 사용자가 접근할 projectId를 통해 서버와 연결과정을 수행합니다. 이 과정에서 최초로 해야할 일은 해당 프로젝트의 컨테이너가 실행중인지 확인하는 것입니다.

/api/projects/{projectId}/run 은 '지금 이 project를 작업하기 위해 IDE를 사용할 것이니 컨테이너를 실행시켜' 라는 의미를 갖는 API입니다. 백엔드 애플리케이션은 이 요청을 받으면 컨테이너를 실행시키고, 그 결과를 응답해야합니다.

하지만 컨테이너를 구동하는 것은 굉장히 오래걸리는 일입니다. 따라서 구동이 완료될 때까지 HTTP 요청을 붙들고 있는 것 보다는, 이후에 사용자에게 웹소켓을 통해 구동이 완료되었다는 정보를 제공해주는 것이 좋을 것이라고 판단했습니다. run api는 컨테이너가 실행중이 아니라면 컨테이너를 실행시키고, 컨테이너의 구동 상태를 사용자에게 반환합니다.

Run API에서는 해당 프로젝트의 컨테이너가 실행중인지 알아야 합니다. 이를 위해 ContainerStore 라는 클래스를 개발하여, 컨테이너의 상태를 관리하게 만들었습니다. ContainerStore에는 프로젝트의 컨테이너가 PENDING 상태인지, RUNNING 상태인지, 아니면 시작되지 않았는지에 대한 정보를 담고 있습니다. Run API는 ContainerStore 로부터 컨테이너의 정보를 얻고 컨테이너를 실행시킬지 아니면 단순히 컨테이너 정보를 반환할지를 결정합니다.

프론트엔드 애플리케이션이 PENDING 상태를 받는 것은 두 가지 경우에 해당할 것입니다.

  1. 프로젝트의 최초 실행 : 백엔드 애플리케이션은 컨테이너를 구동하고 PENDING을 반환
  1. 누군가 프로젝트를 실행시켰지만, 아직 컨테이너가 RUNNING 상태가 아닌 경우 : 백엔드 애플리케이션은 ContainerStore로부터 상태 값(PENDING)을 꺼내어 반환

두 경우 모두 IDE를 사용할 환경이 갖춰지지 않은 경우이므로, 웹소켓 연결을 한 뒤 컨테이너 상태를 전달받는 구웹소켓 구독을 보낸 후 컨테이너의 실행을 기다립니다.

이후 컨테이너가 실행되면 AWS에서 컨테이너 상태 변경 이벤트가 발생하고, 이 이벤트는 Lambda를 실행시켜서 백엔드 애플리케이션으로 정보를 알립니다. 이후 백엔드 애플리케이션은 ContainerStore의 정보를 업데이트한 뒤 해당 프로젝트의 컨테이너 상태를 구독한 사용자에게 그 정보를 알립니다. 이를 통해 PENDING을 받고 기다리던 사용자들은 다음 태스크로 이어갈 수 있습니다. 또한 ContainerStore의 해당 컨테이너 상태는
RUNNING으로 업데이트 되었으므로, 프로젝트에 접근하고자 하는 사용자는 첫 요청에 바로 RUNNING 상태를 응답으로 받아 기다리지 않고 다음 태스크를 수행할 수 있습니다.

프론트엔드 애플리케이션은 컨테이너의 상태가 RUNNING이라면 IDE 렌더링에 필요한 정보들을 받고 실시간 작업 내용 동기화에 필요한 정보를 받는 웹소켓을 구독합니다. 이 과정을 거치면 사용자는 IDE를 통해 작업을 수행할 수 있습니다.

컨테이너의 종료

웹소켓 세션에는 해당 연결이 어떤 사용자로부터 발생했고 어떤 project에 대한 것인지에 대해 저장되어있습니다. 따라서 이것을 통해 특정 프로젝트 IDE에 접속해 있는 인원이 몇명인지 파악할 수 있고, 이 인원이 0명이 되는 순간 컨테이너를 종료할 수 있습니다. 하지만 이러한 방법으로는 완벽하게 생명주기를 관리할 수 없습니다. 일반적인 요청 flow만 발생하면 잘 동작하나, 외부에서 Run API만 호출하는 경우가 발생하면 웹소켓 연결과는 관련이 없어지므로 사용자 없이도 컨테이너가 돌아갈 가능성이 존재합니다.

따라서 이를 관리하기 위해서는 의미없이 돌아가고 있는 컨테이너를 감지하고 이를 종료시키는 배치 시스템을 도입해야할 것으로 예상됩니다. 이는 제한된 시간안에 구현할 수는 없었지만, 추후의 과제로 미뤄두고 싶습니다.

터미널

사용자는 IDE에 접속한 이후 터미널을 통해 프로젝트를 조작할 수 있어야 합니다. 사용자는 지속적으로 나의 터미널이 어떤 디렉토리를 가리키고 있는지 파악할 수 있어야하며, cd, ls와 같은 리눅스 명령어부터 python main.py와 같은 프로그램을 수행시키는 명령어도 수행할 수 있어야 합니다.

터미널 처럼 동작하는 프론트엔드 컴포넌트

프론트엔드 컴포넌트에서 실제로 도커 컨테이너에 연결되어있는 컴포넌트를 만드는 것은 보안적인 측면에서 매우 좋지 않습니다. 따라서 프론트엔드 컴포넌트는 터미널의 외관만을 제공하고, 실질적으로 명령어를 전송하는 것은 백엔드 애플리케이션이 중재하도록 했습니다.

ECS Exec

ECS가 관리하는 컨테이너에 명령어를 전달하고 싶은 경우 ECS Exec이라는 기능을 사용할 수 있습니다. ECS Exec을 사용하면 도커 컨테이너와 세션으로 연결되어, 명령어를 수행한 결과를 확인할 수 있습니다.

https://github.com/The-Great-Sign/IDE-Project-backend/blob/3e5d291b42313243ce45a22ffc6b4675d25953af/src/main/java/goorm/dbjj/ide/container/ContainerUtilImpl.java#L19

명령어의 가공

사용자가 작성하는 명령어는 그대로 도커 컨테이너에 전달할 수 없습니다. 그 이유는 다음과 같습니다.

  • 터미널의 현재 디렉터리 위치 : 일반적으로 터미널을 이용할 때에는 cd와 같은 명령어를 통해 수행할 디렉터리로 위치를 옮긴 뒤 명령어를 수행합니다. 따라서 명령어만 받아서 도커 컨테이너에 전달하는 경우 사용자의 의도를 반영하지 못하게 됩니다.
  • 볼륨의 존재 : 도커 컨테이너는 프로젝트의 디렉터리를 볼륨 마운팅을 통해 갖고 있지만, 사용자의 입장에서 이는 숨겨져야 할 정보입니다. 사용자가 프로젝트의 루트 디렉토리로 이동하고자 한다면 (cd /) 도커의 입장에서는 마운팅된 볼륨의 루트 (cd /{volume-path})로 이동해야 합니다.

이러한 문제를 해결하기 위하여 사용자가 전달한 명령어는 다음과 같은 방식으로 변경되어 컨테이너에 전달됩니다.

사용자 입력 : {
	command : 사용자가 입력한 명령어, ex) python main.py
	path : 사용자 터미널이 현재 가리키고 있는 위치 ex) /src
}

컨테이너 전달 : bash -c '(cd /{volume-path} ; cd .{path} ; {command} ; echo -e {separator} ; pwd)
ex) bash -c 'cd /app ; cd ./src ; python main.py ; echo -e "===XYZ===" ; pwd'

전달되는 명령어가 위와 같이 바뀌는 이유는 다음과 같습니다.

  1. cd /{volume-path} : 사용자의 명령을 수행할 위치를 볼륨 하위로 옮깁니다.
  1. cd .{path} : 사용자의 디렉터리 위치를 받아 해당 위치로 이동합니다.
  1. {command} : 명령어를 수행합니다.
  1. echo -e {separator} : 사용자의 명령어와 pwd의 결과를 구분하기 위해 사용됩니다.
  1. pwd : 사용자의 명령어 이후 변경된 디렉터리 위치를 가져오기 위해 사용됩니다.

이러한 방식으로 명령어를 전달한다면 사용자와 서버는 다음과 같이 통신할 수 있습니다.

출력값 전달

하지만 도커 컨테이너에 명령어를 전달하는 것으로 요구사항을 만족시키기는 어렵습니다. 명령어의 출력은 연결 세션에서 발생하므로, 이를 애플리케이션 서버에서 확인하여 사용자에게 전달하기에는 추가적인 장치가 필요합니다.

출력 내용을 애플리케이션 서버로 가져오기 위해서 출력 값을 받아올 수 있는 API를 개발했고, 도커 컨테이너에서 curl을 통해 API를 호출하여 결과를 전달할 수 있도록 만들었습니다. 이를 위해서는 추가적인 명령어의 가공이 필요합니다.

(가공된 명령어) 2>&1 | curl -d @- {API_URL}?secretKey={secretKey}&userId={userId}&projectId={projectId}
  • 2>&1 : 사용자 명령어에서 에러 출력이 발생한 경우 이를 표준출력으로 바꾸어 curl의 body로 전달합니다.
  • secretKey : 아무나 API를 호출하는 것을 막기 위해서 명령어 생성 시점에 비밀키를 curl 요청의 파라미터에 담습니다. 이 비밀키가 일치하는 경우에만 정상적인 로직으로 간주합니다.
  • userId, projectId : 이 명령어의 수행이 어떤 사용자가 어떤 프로젝트에서 수행한 명령어인지 식별하기 위해 사용됩니다. 이 값들을 통해 결과를 전송해야할 웹소켓 세션을 찾습니다.

결과적으로 만들어지는 명령어는 다음과 같습니다.

요청
client (projectId = abc, userId = 123) {
	path: /src
	command : python hello.py
}
도커 컨테이너에 전달되는 명령어 :
bash -c '(cd /app ; cd ./src ; python hello.py ; echo -e "===XYZ===" ; pwd) 2>&1 | curl -d @- "<https://host/api/execute/output?secretKey=qwe!@#&userId=123&projectId=abc>"'

수행의 결과는 API 호출을 통해 애플리케이션 서버로 들어오며 이는 다시 정제되어 사용자에게 전달됩니다.

파일 및 디렉터리 관리

EFS

EFS 저장소는 다음과 같이 구성되어 있습니다.

(root)/{projectId}/{project-directories-and-files
사용자의 작업공간 디렉터리는 EFS의 {projectId} 폴더의 하위에 디렉터리 구조를 갖춘 파일로 저장됩니다.

각각의 프로젝트 도커 컨테이너는 /{projectId} 하위 디렉터리를 볼륨으로 갖는 EFS AccessPoint를 갖습니다. 따라서 도커 컨테이너 내부에서는 프로젝트의 파일들에 모두 접근할 수 있으며, 다른 프로젝트의 파일로의 접근은 제한됩니다.

사용자의 파일 조작

사용자는 코드를 작성하고 그 결과를 서버에 반영해야합니다. 사용자는 파일 및 디렉터리를 생성하고, 파일을 수정하고, 삭제하며, 파일의 디렉터리 위치를 옮길 수 있어야 합니다.

이를 구현하기 위해서 미리 만들어둔 터미널을 사용하는 방법이 있습니다. 사용자의 요청에 대해 알맞은 명령어로 변환한 뒤 컨테이너에 명령을 보내는 방법입니다. 이후 컨테이너는 사용자의 조작에 대한 명령어를 수행하여 EFS 저장소에 변경된 내용을 반영할 것입니다. 이 방법을 사용하면 격리된 환경으로 인해 다른 프로젝트에 어떠한 방법으로도 영향을 끼치지 못하므로 보안 측면에서 효과적입니다.

하지만 이는 구현에 있어서 어려운 부분이 있었습니다. 파일의 조작 API는 조작에 성공했는지 실패했는지, 실패했다면 왜 실패했는지 상세하게 알려줄 필요가 있다고 판단하여 HTTP API로 개발하기로 했습니다. 이 경우 터미널을 통한 파일 조작 로직은 다음과 같습니다.

  1. 사용자는 파일을 생성하라는 HTTP 요청 보냄
  1. 애플리케이션 서버는 요청에 해당하는 명령어를 만들어 알맞은 프로젝트 컨테이너에 전달
  1. 요청 쓰레드는 컨테이너로부터 응답이 올 때 까지 기다려야함
  1. 컨테이너가 명령을 수행하고 그 결과를 별개의 요청으로 전송
  1. 응답값이 오면 요청 쓰레드는 수행을 이어감

이러한 과정은 하나의 요청이 너무 오래 쓰레드를 붙잡고 있게 되고, 경우에 따라는 디버깅이 어려워질 것이라 판단했습니다. 따라서 파일 조작은 애플리케이션 서버에 EFS 저장소를 마운팅하여 수행하기로 했습니다.

이러한 구조에서 애플리케이션 서버는 로컬에 파일을 생성하고 삭제하는 것과 동일한 방법으로 마운팅 공간에 파일을 생성하고 삭제하며 사용자의 요청을 처리할 수 있습니다. 마운팅 공간은 컨테이너와 공유하고 있는 공간이므로 이 곳에 작성한 파일에 대해 컨테이너가 실시간으로 인지할 수 있게 됩니다.

사용자 간 디렉터리 동기화

사용자는 같은 프로젝트에 접속한 사용자가 파일 및 디렉터리를 변경했을 때, 그 변경내역을 실시간으로 볼 수 있어야 합니다. 이를 위해서 프로젝트의 디렉터리 변경사항을 전달하는 웹소켓 기능을 만들어, 구독자들이 다른 곳에서 변경된 파일 구조에 대한 정보를 받을 수 있게 만들었습니다.

처음에는 HTTP File CRUD API가 호출되었을 때, 변경사항을 해당 프로젝트를 구독하고 있는 사용자에게 전달하는 로직으로 구현할 생각이었습니다. 하지만 이 방법은 File CRUD API가 단순히 파일을 변경하는 책임과 더불어 변경사항을 전달하는 책임을 가지게 됩니다. 또한 사용자가 API를 거치지 않고 터미널을 통해 디렉터리나 파일을 생성하는 경우에 대해서는 처리할 수 없었습니다.

따라서 이를 효과적으로 처리하기 위해서 디렉터리 감지 모듈을 도입했습니다. Apache Commons IO의 FileAlterationObserverFileAlterationMonitor를 통해 EFS 디렉터리의 변경사항을 감지하고 해당 프로젝트의 구독자에게 변경사항을 전달하도록 했습니다. 이를 통해 CRUD API에서 책임을 분리해내고 터미널을 통한 디렉터리 변경 이벤트도 처리하여 사용자에게 전달할 수 있게 되었습니다.

데모 영상

로그인 및 프로젝트 생성

입장

동시편집

파일트리

터미널

챗지피티

채팅

마무리

이 글이 앞으로 비슷한 요구사항의 개발이 필요할 때 도움이 될 수 있었음 좋겠습니다.


Uploaded by N2T