1/1/2023
11/4/2024

The Twelve-Factor App

https://12factor.net/

애플리케이션을 잘 돌아가게, 그리고 쉽게 유지보수 할 수 있도록 하는 개발 방법론

Twelve-Factor app은 아래 특징을 가진 SaaS 앱을 만들기 위한 방법론이다. <생략>

코드 구조에 힌트를 주는 것에 나아가서 개발 문화에 까지도 조언한다. 그래서 조금 부정적인 느낌이 들긴 하지만 공감하게 되는 내용들이다.

I. 코드베이스

버전 관리되는 하나의 코드베이스와 다양한 배포

코드 베이스는 지속 관리하는 코드의 모음을 의미한다. 하나의 코드 베이스로부터 하나의 애플리케이션 만 나와야 하며, 만약 성격이 다른 여러개의 애플리케이션이 나온다면 그건 분산 시스템

따라서 분산 시스템은 각각 개별 앱을 가지며, 개별 앱이 12-factor를 따른다. 개별 앱은 서로 공유하는(중복되는) 코드를 가지면 안되고, 공유되는 코드를 라이브러리화 하여 종속성 매니저로 관리해야 한다.

로컬 테스트, 스테이징, 라이브 서버를 별개의 앱으로 보는 것은 아니다. 이는 데이터베이스의 URL이 다르거나 환경 설정이 상이할 뿐이기 때문이다. 별개의 앱인지 구분하는 방법은 특정 커밋으로부터 분기되어 git cherry-pick 이나 Copy and Paste 와 같은 방법으로 공유 코드를 가지는 것이다.

단일 앱은 버전 관리 시스템에서 모든 브랜치들이 언젠가는 통합될 수 밖에 없다. 개발, 스테이징 브랜치도 결국은 master 브랜치로 rebase, merge 될 것이다.

하나의 코드 베이스와 환경 설정의 조합으로 배포가 발생한다. 배포는 실행중인 인스턴스 말하며, 테스트, 개발, 라이브 서버와 같이 분리하는 방법을 다양한 배포 라고 한다.

분산 시스템과 다양한 배포 이 차이를 잘 알아야 한다고 생각한다.

II. 종속성

명시적으로 선언되고 분리된 종속성

III. 설정

환경(environment)에 저장된 설정

IV. 백엔드 서비스

백엔드 서비스를 연결된 리소스로 취급

V. 빌드, 릴리즈, 실행

철저하게 분리된 빌드와 실행 단계

VI. 프로세스

애플리케이션을 하나 혹은 여러개의 무상태(stateless) 프로세스로 실행

VII. 포트 바인딩

포트 바인딩을 사용해서 서비스를 공개함

VIII. 동시성(Concurrency)

프로세스 모델을 사용한 확장

IX. 폐기 가능(Disposability)

빠른 시작과 그레이스풀 셧다운(graceful shutdown)을 통한 안정성 극대화

X. dev/prod 일치

development, staging, production 환경을 최대한 비슷하게 유지

XI. 로그

로그를 이벤트 스트림으로 취급

XII. Admin 프로세스

admin/maintenance 작업을 일회성 프로세스로 실행

안정된 의존관계 원칙(Stable Dependencies Principle)

DIP를 지킨다고 모든 의존성을 주입받아야 될까? 그런 클래스가 있다면 너무 사용하기 어려울 것이다.

엉클 밥의 principles of component design (한글 자막) 에서 어떤 클래스를 주입 받아야 하는지 알려준다. 주제는 컴포넌트 설계에 대한 내용이다. 우아한 형제들 기술 블로그 안정된 의존관계 원칙과 안정된 추상화 원칙에 대하여 - 손권남님 에서는 안정된 의존관계 원칙에 대해서 집중 조명한다.

String 클래스를 주입받아 사용하지는 않는다. 이러한 유틸 클래스를 모두 주입하면 코드의 복잡도는 더욱 증가할 거 같다.

두 글을 읽어보면 '변경되는', '변경되지 않는' 이라는 말이 자주 나온다. 어째서 String 클래스는 변경되지 않는 안정된 클래스일까?

일단 모든 코드는 변경될 수 있으니까, 불안정하다고 봐야겠네 라고 접근하는 건 아닌게 확실하다.

변경되지 않는다는 말은 용도가 명확하다 라고 생각하면 이해하면 될 거 같다. 자바스크립트에서 String 클래스의 메서드를 사용하는 이유는 명확해 보인다. 정규식으로 특정 문자열을 뽑아내기 위해서 .match()를 쓰고, 특정 범위를 추출하기 위해서 .substr()를 쓴다. 각각 (string, REGEX) => string, (string) => string이다. 다른 변수가 끼어들만한 것은 없어 보인다.

그러니까 여러개의 정책을 가질 필요가 없어보인다. 단 하나의 정책만 있으면 된다고 생각한다.

불안정한 클래스의 대표적인 예는 마틴 파울러의 제어의 역전(IoC)에 대한 글이다. MovieLister 클래스와 MovieFinder 클래스 관계에 대한 이야기가 나온다. 여기서 MovieFinder 클래스는 정책을 가진다. 이름 목록을 텍스트 파일에서 데이터베이스에서 웹 서비스에서, 어디서든 가져올 수 있다. 그래서 MovieFinder 클래스는 추상 클래스가 되어, 내부 구현은 어떻든 이름 목록만 반환하도록 한다.


깃북 관리 페이지를 만들어 보려다가 마크다운 파일의 Front Matter를 파싱해야 하는 일이 생겼다. 마침 npm에 좋은 라이브러리가 있어서 가져다 쓰려고 했다. (front-matter) 이 모듈을 필요로하는 클래스에서 require해서 사용하다가 외부 라이브러리니까 주입 받아야되지 않을까? 라고 생각했다.

결국 특정 상황에 따라서 파싱 라이브러리를 바꾸지는 않겠구나 싶어서 그냥 그대로 두었다. 바꾼다면 라이브러리를 사용하는 클래스를 고치는 편이 낫겠다고 판단했다.