Ruby의 duck typing 장점과 위험은?
공통 인터페이스를 명시 선언 없이 조합해 빠르게 확장할 수 있습니다. 반면 계약이 암묵적이므로 경계에서는 테스트, 명확한 이름, 필요하면 타입 검사 도구로 실패 지점을 앞당겨야 합니다.
PRACTICAL LANGUAGE GUIDE
Ruby와 Rails의 백엔드 제품 개발, 운영 자동화, 설계 원칙과 면접 준비를 정리한 실무 가이드입니다.
새 서비스의 첫 화면을 며칠 안에 띄워야 하는 상황에서 Rails는 여전히 경쟁력이 있습니다. 이유는 언어가 특별해서가 아니라, 웹 애플리케이션이 반복해서 마주하는 문제에 이미 답이 하나씩 정해져 있기 때문입니다. 라우팅 규칙, 폴더 배치, 데이터베이스 마이그레이션 방식, 폼과 검증, 세션 처리까지 팀이 논의할 필요 없이 관례를 따르면 됩니다. 회의에서 사라지는 시간이 코드로 옮겨 가는 셈입니다.
언어 쪽에서 오는 이득도 있습니다. Ruby는 읽었을 때 문장에 가깝게 보이도록 설계된 문법을 갖고 있어, 도메인 규칙을 코드에 그대로 옮기기 쉽습니다. 블록 문법 덕분에 "이 자원을 열고, 무언가를 하고, 반드시 닫는다" 같은 절차를 라이브러리가 감싸 주고 사용하는 쪽은 가운데 로직만 적습니다. 트랜잭션, 파일 처리, 재시도 같은 코드가 짧아지는 것은 이 구조 덕분입니다.
대신 잘 맞지 않는 자리도 분명합니다. 요청 하나하나의 지연 시간이 제품의 경쟁력인 경우, 또는 CPU를 오래 쓰는 계산이 서비스의 본체인 경우에는 다른 선택지를 먼저 봐야 합니다. 특히 표준 구현에는 전역 락이 있어 스레드를 늘려도 Ruby 코드가 여러 코어에서 동시에 실행되지는 않습니다. 입출력 대기 중에는 다른 스레드가 일할 수 있으므로 웹 요청 처리에는 큰 문제가 없지만, 계산량으로 승부하는 작업은 프로세스를 나누거나 다른 언어로 빼는 것이 정직한 해법입니다. 백엔드 전반의 학습 순서를 잡는 중이라면 백엔드 개발 로드맵을 기준으로 삼으세요.
Rails를 고른다는 것은 아키텍처의 상당 부분을 프레임워크에 맡기겠다는 결정입니다. 모델이 데이터베이스 테이블과 붙어 있고, 컨트롤러가 요청을 받고, 뷰가 화면을 만드는 구조는 처음에는 편합니다. 문제는 도메인이 자라면서 시작됩니다. 결제, 권한, 재고처럼 규칙이 복잡한 영역이 모델 하나에 계속 쌓이면 그 파일은 수백 줄이 되고, 콜백이 서로를 부르면서 저장 한 번에 무슨 일이 일어나는지 추적하기 어려워집니다.
해법은 프레임워크와 싸우는 것이 아니라 경계를 하나 더 만드는 것입니다. 규칙에 이름을 붙여 별도 객체로 꺼내고, 모델에는 저장과 조회에 관한 책임만 남깁니다. 콜백으로 자동 실행하던 부수 효과는 명시적인 호출로 바꿉니다. 이렇게 하면 테스트에서 데이터베이스를 거치지 않고 규칙만 검증할 수 있고, 새로 합류한 사람이 코드를 따라 읽을 때 건너뛰는 구간이 줄어듭니다.
타입 선언이 없다는 점도 같은 맥락에서 봐야 합니다. Ruby는 객체가 어떤 클래스인지보다 어떤 메서드에 응답하는지를 봅니다. 덕분에 대역 객체를 끼워 넣기 쉽고 확장도 자유롭지만, 계약이 코드에 적혀 있지 않습니다. 어떤 값이 들어올 수 있는지는 결국 테스트와 이름으로만 남습니다. 그래서 경계가 되는 지점, 즉 외부 API 응답을 받는 곳과 사용자 입력을 받는 곳에서는 형태를 한 번 검사하고 넘기는 습관이 필요합니다.
Ruby에서는 이미 존재하는 클래스를 나중에 다시 열어 메서드를 추가할 수 있습니다. 표준 라이브러리의 문자열 클래스에도 마음대로 메서드를 붙일 수 있습니다. 짧은 스크립트에서는 편하지만 여러 사람이 오래 유지하는 코드베이스에서는 계산이 달라집니다. 어떤 메서드가 어디서 정의됐는지 파일 검색으로 찾을 수 없게 되고, 두 라이브러리가 같은 이름을 각자 붙이면 로딩 순서에 따라 동작이 달라집니다.
정의되지 않은 메서드 호출을 가로채 동적으로 처리하는 기법도 마찬가지입니다. 코드는 줄어들지만 그 자리에서 무엇이 호출 가능한지 읽어 낼 방법이 사라집니다. 오타는 실행 시점에야 드러나고, 디버깅은 스택 추적을 거꾸로 따라가는 작업이 됩니다. 기준을 하나 두면 판단이 쉬워집니다. 이 기법이 없앤 반복 코드의 양이, 나중에 처음 보는 사람이 흐름을 이해하는 데 드는 시간보다 큰지 물어보는 것입니다. 라이브러리를 만드는 쪽이면 답이 예일 때가 많고, 제품 코드를 쓰는 쪽이면 아닐 때가 많습니다.
새로 합류한 사람이 "이 메서드는 어디서 왔나요"라고 묻는 횟수를 메타프로그래밍 비용의 지표로 쓰면 편합니다. 같은 질문이 반복되면 그 부분은 명시적인 코드로 풀어 두는 편이 낫습니다. 프로젝트를 시작할 때 코드 스타일 검사 도구를 붙이고 규칙을 팀 합의로 고정해 두면, 취향 논쟁이 리뷰에서 빠지고 설계 이야기만 남습니다.
class CreateSubscription
def call(user:, plan:)
raise ArgumentError, '이미 구독 중입니다' if user.subscribed?
Subscription.transaction do
subscription = user.subscriptions.create!(plan: plan)
BillingJob.perform_later(subscription.id)
subscription
end
end
end이 클래스가 하는 일은 하나입니다. 이미 구독 중인지 확인하고, 구독을 만들고, 결제는 나중에 처리하도록 넘깁니다. 컨트롤러나 모델 콜백이 아니라 별도 객체로 꺼낸 이유는 이 규칙에 이름을 주기 위해서입니다. 이름이 있으면 테스트 파일도 찾기 쉽고, 나중에 무료 체험이나 쿠폰 같은 조건이 붙어도 붙일 자리가 분명합니다. 외부 결제 호출을 트랜잭션 안에서 직접 하지 않고 잡으로 넘긴 것도 의도적입니다. 결제 게이트웨이 응답이 느리면 데이터베이스 트랜잭션이 그만큼 열려 있게 되고, 실패했을 때 재시도할 방법도 마땅치 않기 때문입니다. 클래스와 객체 설계를 언어별로 비교해 보고 싶다면 클래스 문법 비교가 참고가 됩니다.
루비 버전은 프로젝트마다 다르므로 버전 관리 도구로 고정합니다. 의존성은 Gemfile에 적고 잠금 파일을 저장소에 함께 넣습니다. 이 두 가지만 지켜도 "제 컴퓨터에서는 됩니다" 부류의 문제 대부분이 사라집니다. 젬을 고를 때는 최근에 손이 닿았는지, 사용하는 프레임워크 버전을 지원하는지를 먼저 봅니다. 오래 방치된 젬 하나가 프레임워크 업그레이드 전체를 막는 일이 드물지 않습니다.
테스트 문화는 이 생태계의 강점입니다. RSpec이든 Minitest든 팀이 하나를 정하고 일관되게 쓰면 됩니다. 타입 검사가 잡아 주지 않는 부분을 테스트가 대신 받쳐 주는 구조이므로, 테스트가 얇은 Ruby 코드베이스는 리팩터링이 빠르게 무서워집니다. 여기에 스타일 검사 도구를 붙여 형식을 자동화하고, 데이터베이스 변경은 마이그레이션 파일로만 반영합니다. 콘솔에서 직접 스키마를 고치기 시작하면 환경 간 차이를 추적할 방법이 사라집니다.
운영 환경에서는 애플리케이션 서버가 여러 프로세스를 띄우고 각 프로세스가 스레드를 갖는 구조를 쓰게 됩니다. 프로세스 수는 메모리에, 스레드 수는 데이터베이스 연결 수에 묶여 있으므로 두 값을 따로 정하면 커넥션 풀이 먼저 바닥납니다. 메일 발송, 이미지 처리, 외부 API 호출처럼 느리거나 실패할 수 있는 작업은 요청 경로에서 빼서 백그라운드 잡으로 넘깁니다. 잡은 언제든 다시 실행될 수 있다고 가정하고 같은 입력에 두 번 실행돼도 결과가 달라지지 않게 만들어야 합니다.
성능 문제는 대부분 언어가 아니라 쿼리에서 나옵니다. 목록 화면에서 각 행이 연관 데이터를 다시 조회하는 패턴은 로컬의 작은 데이터에서는 보이지 않다가 운영에서 갑자기 드러납니다. 로그와 모니터링 도구로 실제 쿼리 수를 확인하고, 필요한 관계만 미리 읽고, 인덱스와 페이지 크기를 함께 조정합니다. 여기까지 하고도 부족하면 그때 캐시나 다른 언어를 검토해도 늦지 않습니다.
아래 항목을 지금 상황에 대입해 보고 결정하세요.
INTERVIEW PREP
공통 인터페이스를 명시 선언 없이 조합해 빠르게 확장할 수 있습니다. 반면 계약이 암묵적이므로 경계에서는 테스트, 명확한 이름, 필요하면 타입 검사 도구로 실패 지점을 앞당겨야 합니다.
목록에서 각 행이 연관 데이터를 다시 조회하는 패턴을 로그·APM으로 확인합니다. eager loading을 적용하되 필요한 관계만 미리 읽고, 인덱스와 페이지 크기까지 함께 검토합니다.
NEXT STEP