Skip to main content

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

구분BaseExtensions
필수 여부필수 (모든 프로젝트)선택 (프로젝트별)
예시Permission, Tenant, Auth, AuditGuardian, 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 변경 시 전체없음
커스터마이징제약 있음자유로움

안전성 우선: 운영 중인 프로젝트에 의도치 않은 변경이 발생하지 않습니다.

운영 원칙

  1. _template 고도화: 새 기능/버그 수정은 먼저 템플릿에 적용
  2. 신규 프로젝트: 최신 _template 복사로 시작
  3. 기존 프로젝트: 필요시 선택적 백포트 (수동)
  4. 안전성 우선: 기존 프로젝트는 건드리지 않음

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-*/

현재 원칙 (패키지화 대비)

지금부터 이 원칙을 지키면 나중에 최소 비용으로 전환 가능합니다:

  1. Core에서 프로젝트 모델 직접 참조 금지 → Interface 사용
  2. 설정값은 config 파일로 분리config/core.php
  3. 확장 포인트 설계 → Event, Hook, Middleware 활용
  4. 의존성 최소화 → Core는 Laravel 기본 기능만 의존

버전 관리

Core 버전

Core는 packages/core/version.json에서 버전을 관리합니다.

{
"version": "0.5.0",
"updated_at": "2026-01-11"
}

버전 정책

버전 유형변경 시점예시
Major (X.0.0)Breaking ChangeAPI 변경
Minor (0.X.0)새 기능 추가새 Extension
Patch (0.0.X)버그 수정오류 수정

프로젝트별 버전 추적

각 프로젝트는 생성 시점의 Core 버전을 기록합니다.

// workspace/myapp/.core-version
{
"created_with": "0.5.0",
"current": "0.5.0",
"created_at": "2026-01-11"
}

선택적 백포트

기존 프로젝트에 새 기능을 적용하고 싶다면 선택적 백포트를 수행합니다.

백포트 프로세스

# 1. 변경 내용 확인
git diff HEAD~5 packages/core

# 2. 필요한 변경만 선택 적용
# (수동으로 프로젝트에 복사)

# 3. 테스트
make test NAME=myapp

# 4. 버전 업데이트
# .core-version의 current 값 수정

주의사항

  • 전체 Core를 덮어쓰지 않음 (변경된 파일만)
  • 프로젝트별 커스터마이징이 있으면 충돌 확인
  • 테스트 필수 실행

관련 문서