Monorepo 접근법
📝 초안 (Draft)
이 문서는 검토 중입니다. 내용이 변경될 수 있습니다.
Multi-SaaS Kit은 Monorepo 구조로 Core 모듈과 Plugins를 중앙 관리하면서, 각 프로젝트의 독립성을 보장합니다.
핵심 개념
왜 Monorepo인가?
여러 SaaS 프로젝트를 운영할 때, 공통 기능(권한, 인증, 테넌트 격리 등)을 중복 없이 관리하면서도 프로젝트별 커스터마이징이 가능해야 합니다.
| 접근법 | 장점 | 단점 |
|---|---|---|
| Multirepo (별도 리포) | 완전 독립 | 코드 중복, 동기화 어려움 |
| Composer 패키지 | 버전 관리 | 인프라 필요, 커스터마이징 제한 |
| Monorepo (선택) | 중앙 관리 + 유연성 | 리포 크기 증가 |
Multi-SaaS Kit은 Monorepo + 심볼릭 링크 방식을 채택했습니다.
구조 개요
multi-saas-kit/
├── packages/ # ⭐ 중앙 관리 영역
│ ├── core/ # Core 모듈
│ │ ├── Base/ # 필수 모듈 (14개)
│ │ ├── Extensions/ # 선택 모듈 (4개)
│ │ ├── Contracts/ # Interface 정의
│ │ └── Providers/ # CoreServiceProvider
│ │
│ └── plugins/ # Plugins (수익화 대상)
│ ├── TwoFactorAuth/
│ └── Subscription/
│
├── workspace/
│ ├── _template/ # 프로젝트 템플릿
│ │ └── web/app/
│ │ └── Core → packages/core # 심볼릭 링크
│ │
│ └── myapp/ # 실제 프로젝트
│ └── web/app/
│ └── Core → packages/core # 심볼릭 링크
packages/ 구조
Core 모듈
Core는 모든 SaaS 프로젝트에서 공통으로 사용하는 핵심 기능입니다.
packages/core/
├── Base/ # 필수 모듈 (모든 프로젝트)
│ ├── Permission/ # 권한 시스템 (Level 0~6, ADR-058)
│ ├── Tenant/ # RLS 기반 테넌트 격리
│ ├── Auth/ # 인증 (Sanctum 확장)
│ └── Audit/ # 감사 로그
│
├── Extensions/ # 선택 모듈 (필요한 프로젝트만)
│ ├── Guardian/ # 보호자 관계 (교육, 의료)
│ ├── Impersonate/ # 사용자 전환
│ ├── AdminOverride/ # 관리자 오버라이드
│ └── FeatureFlag/ # 기능 가시성 제어
│
├── Contracts/ # Interface 정의
│ ├── TierableInterface.php
│ └── AuditableInterface.php
│
├── Providers/
│ └── CoreServiceProvider.php
│
└── config/
└── core.php # Core 설정
Base vs Extensions
| 구분 | Base | Extensions |
|---|---|---|
| 필수 여부 | 필수 (모든 프로젝트) | 선택 (프로젝트별) |
| 예시 | Permission, Tenant, Auth, Audit | Guardian, Impersonate |
| 활성화 | 항상 활성화 | config로 활성화/비활성화 |
| 의존성 | 독립적 | Base에 의존 가능 |
// config/core.php
return [
'extensions' => [
'guardian' => env('CORE_GUARDIAN_ENABLED', false),
'impersonate' => env('CORE_IMPERSONATE_ENABLED', true),
],
];
Plugins
Plugins는 수익화 대상 기능으로, 유료 판매됩니다.
packages/plugins/
├── TwoFactorAuth/ # 2FA 인증
├── Subscription/ # 구독 관리
├ ── Invoice/ # 청구서 발행
├── Notification/ # 알림 시스템
└── Analytics/ # 분석 대시보드
심볼릭 링크 방식
작동 원리
# 프로젝트에서 Core 참조
workspace/myapp/web/app/Core → ../../../../packages/core
workspace/myapp/web/app/Plugins → ../../../../packages/plugins
장점:
- Core 수정 시 모든 프로젝트에 즉시 반영
- 개발 중 수정-테스트 사이클 빠름
- 파일 복사 없이 공유
고객 배포 시
고객에게 프로젝트를 배포할 때는 심볼릭 링크를 실제 파일로 변환합니다.
# 고객 배포용 프로젝트
workspace/customer-project/
└── web/app/
└── Core/ # 복사본 (심볼릭 링크 해제)
# 링크 해제 명령 (예시)
cp -rL workspace/myapp/web/app/Core workspace/customer-project/web/app/Core
복사 후 시작 방식
개념
Multi-SaaS Kit은 "복사 후 시작" 방식을 채택했습니다.
_template (고도화) → 새 프로젝트 (최신 기능)
│
├─→ 복사 → project-a (2025-01)
├─→ 복사 → project-b (2025-03)
└─→ 복사 → project-c (2025-06) ← 가장 최신
- 새 프로젝트: 항상 최신
_template에서 시작 - 기존 프로젝트: 영향 없음 (안전)
- 백포트: 필요시 선택적으로 적용 (수동)
왜 이 방식인가?
| 방식 | Core 공유 | 복사 후 시작 (선택) |
|---|---|---|
| 업데이트 전파 | 자동 (위험) | 수동 (안전) |
| 프로젝트 독립성 | 낮음 | 높음 |
| 기존 프로젝트 영향 | Core 변경 시 전체 | 없음 |
| 커스터마이징 | 제약 있음 | 자유로움 |
안전성 우선: 운영 중인 프로젝트에 의도치 않은 변경이 발생하지 않습니다.
운영 원칙
- _template 고도화: 새 기능/버그 수정은 먼저 템플릿에 적용
- 신규 프로젝트: 최신
_template복사로 시작 - 기존 프로젝트: 필요시 선택적 백포트 (수동)
- 안전성 우선: 기존 프로젝트는 건드리지 않음
Core 독립성 규칙
Core가 프로젝트 코드에 의존하지 않도록 설계해야 패키지화 전환이 쉽습니다.
규칙 1: 프로젝트 모델 직접 참조 금지
// ❌ 잘못된 예시 - Core가 프로젝트 모델에 의존
class TierPermission
{
public function check($id)
{
$product = \App\Models\Product::find($id); // 프로젝트 모델 직접 참조
}
}
// ✅ 올바른 예시 - 추상화에만 의존
class TierPermission
{
public function check(Model $model)
{
return $model->tenant_id === auth()->user()->tenant_id;
}
}
규칙 2: Interface 사용
// packages/core/Contracts/TierableInterface.php
interface TierableInterface
{
public function getTenantId(): ?string;
public function getOwnerId(): ?int;
}
// 프로젝트 모델에서 구현
class Product extends Model implements TierableInterface
{
public function getTenantId(): ?string
{
return $this->tenant_id;
}
public function getOwnerId(): ?int
{
return $this->user_id;
}
}
규칙 3: config/env로 설정 주입
// ❌ 하드코딩
class TierService
{
private $maxTiers = 7;
}
// ✅ config 사용
class TierService
{
public function getMaxTiers(): int
{
return config('core.permission.max_tiers', 7);
}
}
규칙 4: 이동성 보장
Core 폴더만 복사해도 동작해야 합니다.
체크리스트:
- Core 내부에서
app/Models/*직접 참조 없음 - Core 내부에서
app/Http/*직접 참조 없음 - 모든 설정은 config 파일로 주입
- 의존하는 Laravel 기본 클래스만 사용
미래: Composer 패키지 전환
현재는 심볼릭 링크 방식이지만, 규모가 커 지면 Composer 패키지로 전환할 수 있습니다.
전환 조건
| 조건 | 현재 | 전환 기준 |
|---|---|---|
| 운영 프로젝트 수 | 1-3개 | 5개 이상 |
| Core 업데이트 빈도 | 수시 | 월 2회 이상 |
| 팀 규모 | 1-2명 | 3명 이상 |
전환 시 필요 인프라
Composer 패키지 전환 필요사항:
- Private Packagist 또는 Satis
- CI/CD 파이프라인 (패키지 빌드/배포)
- Semantic Versioning 엄격 준수
- 변경 로그 관리
전환 후 구조
# Composer 패키지 설치 방식
composer require multisaas/core:^1.0
composer require multisaas/plugins-subscription:^1.0
# 프로젝트 구조
workspace/myapp/
└── web/
└── vendor/
└── multisaas/
├── core/ # Composer로 설치
└── plugins-*/
현재 원칙 (패키지화 대비)
지금부터 이 원칙을 지키면 나중에 최소 비용으로 전환 가능합니다:
- Core에서 프로젝트 모델 직접 참조 금지 → Interface 사용
- 설정값은 config 파일로 분리 →
config/core.php - 확장 포인트 설계 → Event, Hook, Middleware 활용
- 의존성 최소화 → Core는 Laravel 기본 기능만 의존
버전 관리
Core 버전
Core는 packages/core/version.json에서 버전을 관리합니다.
{
"version": "0.5.0",
"updated_at": "2026-01-11"
}
버전 정책
| 버전 유형 | 변경 시점 | 예시 |
|---|---|---|
| Major (X.0.0) | Breaking Change | API 변경 |
| Minor (0.X.0) | 새 기능 추가 | 새 Extension |
| Patch (0.0.X) | 버그 수정 | 오류 수정 |