모듈식 Rails SaaS 애플리케이션을 구조화하는 방법

발행: (2026년 3월 11일 PM 12:00 GMT+9)
13 분 소요
원문: Dev.to

Source: Dev.to

Rails SaaS architecture diagram

이전 글에서 저는 5 년 전만큼 가지고 싶었던 Rails SaaS 아키텍처에 대해 썼습니다.
핵심 아이디어는 간단했습니다: Rails 애플리케이션이 실제 SaaS 제품으로 성장하면, 문제는 더 이상 기능을 빠르게 작성하는 것만이 아니게 됩니다.

진정한 도전은 시스템을 이해하기 쉽게 유지하는 것이 됩니다.

그것은 자연스럽게 다음 질문으로 이어집니다:

그 구조가 실제로 어떻게 보이는가?

이 글은 그 질문에 대한 제 시도입니다. 이것이 Rails 앱을 조직하는 유일한 방법은 아닙니다; 다만 현재 여러 비즈니스 기능, 내부 도구, 장기적인 성장을 가진 제품에 가장 적합하다고 생각되는 구조일 뿐입니다.

기본 성장 경로의 문제점

대부분의 Rails 애플리케이션은 매우 합리적인 구조로 시작합니다:

app/
config/
db/
lib/

시작 단계에서는 잘 작동합니다.

하지만 제품이 성장함에 따라 많은 비즈니스 기능들이 동일한 애플리케이션 레이어에 나란히 존재하게 됩니다:

  • 인증
  • 역할 및 권한
  • 알림
  • 대시보드
  • 감사
  • 지원 티켓
  • 파일 관리
  • 청구 로직
  • 관리자 도구

이 시점에서 문제는 Rails가 나쁘다는 것이 아니라, 모든 것이 동일한 앱 경계 안에서 공간을 차지하려고 경쟁하기 시작한다는 것입니다.

  • 모델이 너무 많은 것을 인식하게 됩니다.
  • 컨트롤러가 관련 없는 관심사를 조정하기 시작합니다.
  • 헬퍼가 이상한 방향으로 성장합니다.
  • 컨선(Concern)이 늘어납니다.

결국 개별 기능이 그리 복잡하지 않음에도 불구하고 애플리케이션이 크게 느껴지게 됩니다.

전환: 역량에 따른 구조화

저에게 더 잘 맞았던 방법은 비즈니스 역량 별로 시스템을 구조화하는 것이었습니다. 단순히 기술 계층만으로 나누는 것이 아니라 말이죠.

따라서 다음과 같이만 생각하는 대신에:

  • 모델
  • 컨트롤러

다음과 같이 생각합니다:

  • 지원
  • 감사
  • 관리
  • 계정
  • 사용자
  • 대시보드
  • 청구

각각의 역량마다 자체 경계를 갖게 됩니다. Rails에서는 이를 가장 깔끔하게 구현할 수 있는 방법이 엔진(engines) 을 사용하는 것입니다.

간단한 고수준 구조

모듈식 Rails SaaS 애플리케이션은 다음과 같이 보일 수 있습니다:

my_app/
├── app/
├── config/
├── db/
├── lib/
├── engines/
│   ├── lesli_core/
│   ├── lesli_admin/
│   ├── lesli_audit/
│   ├── lesli_billing/
│   ├── lesli_dashboard/
│   ├── lesli_shield/
│   └── lesli_support/
└── Gemfile

메인 애플리케이션은 여전히 존재하지만, 이제 모든 기능이 쏟아지는 장소는 아닙니다.
대신, 메인 앱은 통합 계층 역할을 하며, 엔진들은 실제 비즈니스 기능을 포함합니다.

메인 앱에 포함되어야 할 것?

이 부분이 가장 중요합니다. 모듈식 구조는 메인 앱이 규칙을 지킬 때만 제대로 작동합니다. 제 경우 메인 Rails 앱은 보통 다음을 담당합니다:

  • 환경 설정
  • 배포 설정
  • 부팅 프로세스
  • 엔진 마운팅
  • 앱‑특화 브랜딩 및 오버라이드
  • 제품‑특화 커스텀 로직
  • 시스템 최종 구성

따라서 앱 자체는 여전히 중요하지만, 이제 모든 도메인을 직접 소유한다고 가장하지는 않습니다.

Source:

엔진에 무엇이 포함되어야 할까?

각 엔진은 명확한 기능을 가집니다. 예를 들어, support 엔진은 다음과 같은 구조를 가질 수 있습니다:

engines/lesli_support/
├── app/
│   ├── controllers/
│   ├── models/
│   ├── views/
│   └── components/
├── config/
│   └── routes.rb
├── db/
│   └── migrate/
├── lib/
│   └── lesli_support/
└── lesli_support.gemspec

그 엔진은 다음을 포함할 수 있습니다:

  • 티켓
  • 댓글 또는 토론
  • 상태
  • 우선순위
  • 할당 흐름
  • 지원 전용 대시보드
  • 지원과 관련된 알림

이렇게 하면 매우 가치 있는 지역적 추론이 가능해집니다. 지원 작업을 해야 할 때, 다른 레이어에 섞여 있는 티켓 로직을 찾기 위해 거대한 애플리케이션을 뒤지지 않고 바로 지원 엔진으로 이동할 수 있습니다.

코어 레이어의 역할

나는 여전히 공유 코어 레이어를 선호하지만, 작고 의도적으로 유지해야 한다. 내게 코어 엔진은 보통 다음과 같은 것들을 포함한다:

  • 진정으로 횡단되는 공유 관심사
  • 공유 UI 프리미티브
  • 기본 클래스
  • 공통 헬퍼
  • 플랫폼 구성 헬퍼
  • 엔진 간 공통 인터페이스

내가 피하려는 것은 코어 레이어를 두 번째 모놀리스로 만드는 것이다. core가 모든 엔진이 공유 단축키를 버리는 장소가 되면, 아키텍처는 서서히 같은 문제로 되돌아간다. 그래서 나는 계속 스스로에게 묻는다:

이것이 정말 횡단적인가, 아니면 더 깔끔한 경계를 피하고 있는 것인가?

라우팅 및 구성

이 접근 방식에서 내가 좋아하는 점 중 하나는 구성이 명시적으로 유지된다는 것이다. 메인 애플리케이션이 무엇을 마운트할지 결정한다. 간소화된 예시:

# config/routes.rb
Rails.application.routes.draw do
  mount LesliAdmin::Engine    , at: "/admin"
  mount LesliSupport::Engine  , at: "/support"
  mount LesliAudit::Engine    , at: "/audit"
end

그것은 최종 애플리케이션 형태를 이해하기 쉽게 만든다. 제품은 미스터리가 아니라; 기능들의 조합이다.

경계는 재사용보다 더 중요합니다

엔진의 좋은 부수 효과 중 하나는 재사용이지만, 그것이 내가 엔진을 좋아하는 주된 이유는 아닙니다. 더 큰 이점은 강제된 경계입니다.

경계가 없으면 모든 기능이 결국 다른 모든 것에 침투하게 됩니다.
경계가 있으면 통합은 의도적으로 이루어져야 합니다.

이것은 코드베이스가 성장하는 방식을 바꿉니다. 더 나은 질문을 스스로에게 하게 됩니다:

  • 이 로직 조각이 기존 엔진에 속해야 할까요, 아니면 자체 기능을 가져야 할까요?
  • 엔진들은 어떻게 긴밀하게 결합되지 않으면서 소통할 수 있을까요?
  • 무엇이 핵심 레이어에 속하고, 무엇이 특정 엔진에 속해야 할까요?

이러한 질문들을 앞에 두고 고민함으로써, 아키텍처는 모듈화되고 유지보수가 쉬우며 장기적인 성장에 대비할 수 있습니다.

이런 의존성이 존재할 수 있을까?

  • 지원팀이 실제로 청구 내부 구조를 알아야 할까요?
  • 이 관심사가 공유된 것인가, 아니면 잘못 배치된 것인가?
  • 이 로직은 앱, 엔진, 혹은 코어 레이어 중 어디에 있어야 할까요?

그러한 질문들은 어떤 명명 규칙보다도 아키텍처를 개선합니다.

이것은 무료가 아니다

공정하게 말하자면, 이러한 구조는 어느 정도 오버헤드를 추가합니다.
다음과 같은 사항들을 더 많이 고민해야 합니다:

  • 엔진 명명
  • 도메인 경계
  • 의존성 방향
  • 엔진 간 마이그레이션
  • 공유 컨벤션
  • 로컬 개발 워크플로우

따라서 모든 프로젝트에 사용하지 않을 것입니다.

작은 내부 도구, MVP, 혹은 매우 빠르게 배포해야 하는 무언가를 만들고 있다면, 이 구조는 너무 이른 시점에 과도하게 느껴질 수 있습니다.

하지만 제품이 여러 기능에 걸쳐 성장하기 시작하면, 그 추가 구조는 무겁게 느껴지는 것이 아니라 유용하게 느껴지게 됩니다.

이 접근 방식에서 가장 마음에 드는 점

내가 가장 마음에 드는 것은 그것이 더 “고급”처럼 느껴진다는 것이 아니다.
시스템을 더 쉽게 다룰 수 있게 만든다는 점이다.

  • 기능을 추가할 때, 그것이 어디에 들어가야 할지 더 명확하게 파악할 수 있다.
  • 디버깅을 할 때, 관련 없는 앱의 다른 부분을 뛰어다니지 않는다.
  • 리팩터링을 할 때, 주변을 모두 깨뜨리지 않을 것이라는 약간의 자신감이 생긴다.

내게 있어 좋은 아키텍처가 해야 할 일이다.

  • 추상화 점수를 얻기 위해서가 아니다.
  • 다이어그램에서 인상적으로 보이기 위해서가 아니다.
  • 단지 앱이 성장함에 따라 이해하기 쉽게 만드는 것이다.

이것이 Lesli와 연결되는 방식

Lesli 아키텍처 다이어그램

이는 Lesli를 구축하면서 탐구해 온 전반적인 방향과 동일합니다.

Rails를 더 복잡하게 만들려는 것이 아닙니다.
주로 더 큰 SaaS‑스타일 애플리케이션에 더 깔끔한 성장 공간을 제공하려는 것입니다.

Lesli는 아직 진화 중이지만, 이 모듈식 접근 방식은 그 배경에 있는 아이디어 중 하나입니다.

0 조회
Back to Blog

관련 글

더 보기 »

U+237C ⍼ 은 방위각인가

번역할 텍스트를 제공해 주시겠어요? 해당 기사나 요약문을 복사해서 보내주시면 한국어로 번역해 드리겠습니다.