洪民憙 (홍민희) 블로그

개발환경 구성

개발환경 구성이 번거로우면 사람들이 실제 운영 서버에 막 접속해서 작업을 하거나 하는 일이 빈번해지기 시작한다. 한번 구성해둔 것도 더 잘 깨먹게 된다. CI 돌리기도 난감해진다. 그래서 근래 몇년 사이에 습관이 몇가지 생겼다.

첫째, 언어를 정했으면 다른 언어를 웬만하면 섞지 않는다. 언어를 섞는 경우, 메인 언어로 호스팅되는 다른 언어 구현체가 있어야 한다. 예를 들어 Ruby를 쓴다면 Haml이나 Sass는 Ruby로 작성된 구현체가 있으므로 개발환경 구성이 더 어려워지지 않는다. Clojure를 쓰는데 LESS를 쓰려면 node.js 스택을 끌고 와야 한다. 이럴 때는 기각하고 같은 일을 할 수 있는 다른 라이브러리나 언어 구현체를 찾는다.

둘째, 서비스 의존도는 최소화한다. 이를테면 같은 일을 하는데 관계형 데이터베이스에 의존하는 방법과 의존하지 않는 방법이 있다면 후자를 더 선호한다. 하지만 아무런 서비스에도 의존하지 않고 큰 애플리케이션을 만드는 것은 힘이 든다. 그래서 실질적인 대안은…

셋째, 특정 서비스가 필요하면 그 서비스로 해야 할 일을 정리해서, 그 일을 할 수 있는 대안적인 서비스 여러개를 고려해둔다. 예를 들어 관계형 데이터베이스가 필요하면 일단 PostgreSQL, MySQL, SQLite, Oracle 등 가운데 딱 하나만 쓴다는 가정을 하지 않고 최대한 여러 벤더에서 돌아는 가게 호환 코드를 유지한다. 이때 중요한 점은, 모든 벤더에서 잘 작동해야 한다는 점이 아니다. 실제 운영을 할 때 PostgreSQL을 쓰기로 결정했다고 가정하자. 그럼 나머지 벤더 위에서는 최적화가 안되고 느려도 상관 없다. 그냥 돌아가고 자동화된 테스트를 통과할 정도만 되면 된다. 관계형 데이터베이스를 예로 들었는데 대부분의 언어는 벤더 중립적인 DB API를 가지고 있거나 ORM 차원에서 제공을 하니까 약간의 노력만 들이면 가능한 일이다. 내가 StyleShare에서 했던 일 중 하나는 이미지 스토리지를 위한 인터페이스를 만드는 것이었다. 실제 운영에는 S3를 사용했지만, 인터넷이 안 되는 환경에서 개발을 할 때가 많았기 때문에 애플리케이션 코드에서 직접 S3를 사용하지 않게 했다. 그래서 로컬 개발환경에서는 파일 시스템을 사용하도록 구성했다. 캐시의 경우 werkzeug.contrib.cache 인터페이스를 인용했다. 실제 운영에서 Redis ↔ Memcached 전환을 애플리케이션 코드 수정을 전혀 하지 않고 할 수 있었던 기억이 난다.

넷째, 의존성은 명령어 한두개로 해결할 수 있어야 한다. 이 부분 때문에 제대로 패키징되지 않은 라이브러리는 선택에서 탈락시켰다. Python이라면 표준 distutils/setuptools로 패키징이 되어 있어야 하고, node.js라면 npm으로, Ruby라면 gem으로 패키징되어 있어야 한다. 가끔 로컬 패치를 적용한 포크를 써야 하는 경우가 있다. 다른 언어에서는 어떻게 하는지 모르겠고, Python setuptools에서는 dependency_links를 쓰면 특정 포크를 사용하게 만들 수 있다. (나는 pip -r requirements.txt로 의존성 해결 안하고 setup.py 파일 만들어서 패키징한다.)

이러한 노력을 해도 서비스를 하다보면 결국 의존성이 늘어나고 구성이 복잡해지기 마련이다. 하지만 이런 노력을 아예 하지 않으면 의존성은 방만하게 늘어나고 개발환경 구성은 기존 개발자의 코칭 없이는 혼자서 설치할 수 없을 정도로 복잡해지게 된다. 늘 개발환경 구성은 문서를 읽는 것으로 커버가 되어야 한다고 생각하기 때문에 저 지경으로 복잡해지는 것은 악몽이다.