플러그인 시스템 개요
📝 초안 (Draft)
이 문서는 검토 중입니다. 내용이 변경될 수 있습니다.
Multi-SaaS Kit의 플러그인 아키텍처를 소개합니다.
개요
플러그인 시스템은 Core 기능을 확장하여 추가 기능을 제공하는 모듈화된 아키텍처입니다. 각 플러그인은 독립적으로 설치, 활성화, 비활성화할 수 있습니다.
Core vs Extensions vs Plugins
| 구분 | 역할 | 예시 |
|---|---|---|
| Core (Base) | 필수 기능 | Permission, Tenant, Auth, Audit |
| Extensions | 선택적 Core 확장 | Guardian, Impersonate |
| Plugins | 독립 기능 모듈 | TwoFactorAuth, PasswordPolicy, Subscription |
플러그인의 특징
- 독립성: 다른 플러그인 없이 단독 동작 가능 (L0)
- 수익화: Plugin Store를 통한 판매 가능
- 버전 관리: 의존성 버전 관리
- 동적 로딩: 런타임에 활성화/비활성화
플러그인 티어
플러그인은 4개 티어로 분류됩니다.
티어 구조
| 티어 | 설명 | 의존성 |
|---|---|---|
| L0 | 독립 플러그인 | Core만 의존 |
| L1 | 복합 플러그인 | Core + Extensions 의존 |
| Business | 비즈니스 플러그인 | 수익화 핵심 기능 |
| Enterprise | 엔터프라이즈 | 대기업 특화 기능 |
L0 플러그인 (독립)
Core에만 의존하며 가장 간단한 구조입니다.
| 플러그인 | 설명 |
|---|---|
| TwoFactorAuth | 2단계 인증 |
| PasswordPolicy | NIST 기반 비밀번호 정책 |
| MenuPermissionSync | 메뉴-권한 동기화 |
| Honeypot | 스팸 방지 |
| CodeIntegrity | 코드 무결성 검사 |
| TSVectorSearch | PostgreSQL 전문 검색 |
L1 플러그인 (복합)
Extensions에 의존하는 고급 플러그인입니다.
| 플러그인 | 의존 Extension |
|---|---|
| GuardianExtension | Guardian |
| Analytics | 분석 기반 |
| Notification | 알림 시스템 |
| AdvancedSecurity | 보안 확장 |
Business 플러그인
비즈니스 기능을 제공합니다.
| 플러그인 | 설명 |
|---|---|
| I18n | 다국어 지원 |
| ApiWebhooks | API Webhook |
| Subscription | 구독 결제 |
| PGVectorSearch | AI 벡터 검색 |
Enterprise 플러그인
대기업 고객을 위한 특화 기능입니다.
| 플러그인 | 설명 |
|---|---|
| SaasCustomization | SaaS별 커스터마이징 |
| WhiteLabel | 화이트 라벨링 |
| MultiDatabase | 멀티 데이터베이스 |
가격 모델
플러그인은 3가지 가격 모델을 지원합니다.
// PriceModel Enum
enum PriceModel: string
{
case FREE = 'free'; // 무료
case ONE_TIME = 'one_time'; // 일회성 구매
case SUBSCRIPTION = 'subscription'; // 구독
}
| 모델 | 설명 | 예시 |
|---|---|---|
| Free | 무료 제공 | PasswordPolicy, Honeypot |
| One-time | 일회성 구매 | 기본 기능 플러그인 |
| Subscription | 월/연 구독 | Enterprise, Premium 기능 |
플러그인 구조
폴더 구조
packages/plugins/{PluginName}/
├── plugin.json # 플러그인 메타데이터
├── src/
│ ├── {PluginName}Plugin.php # 메인 플러그인 클래스
│ ├── {PluginName}ServiceProvider.php
│ ├── Contracts/ # 인터페이스
│ ├── Services/ # 서비스 로직
│ ├── Config/ # 설정 파일
│ └── ...
├── database/migrations/ # 마이그레이션
├── tests/ # 테스트
└── README.md
plugin.json 스키마
{
"slug": "password-policy",
"name": "Password Policy",
"description": "NIST compliant password policy",
"version": "1.0.0",
"author": "multi-saas-kit",
"license": "MIT",
"tier": "L0",
"price": {
"model": "free",
"amount": 0
},
"dependencies": {
"core": ">=1.1.0",
"extensions": []
},
"providers": [
"PasswordPolicyServiceProvider"
]
}
BasePlugin 클래스
모든 플러그인은 BasePlugin 추상 클래스를 상속합니다.
namespace App\Plugins\MyPlugin;
use App\Core\Base\Plugin\BasePlugin;
use App\Core\Base\Plugin\Enums\PluginTier;
use App\Core\Base\Plugin\Enums\PriceModel;
class MyPlugin extends BasePlugin
{
protected string $slug = 'my-plugin';
protected string $name = 'My Plugin';
protected string $description = 'My custom plugin';
protected string $version = '1.0.0';
public function boot(): void
{
// 플러그인 부팅 시 실행
}
public function register(): void
{
// 서비스 등록
}
public function getTier(): PluginTier
{
return PluginTier::L0;
}
public function getPriceModel(): PriceModel
{
return PriceModel::FREE;
}
public function getDependencies(): array
{
return [
'core' => '>=1.1.0',
];
}
}
필수 구현 메서드
| 메서드 | 설명 |
|---|---|
getSlug() | 플러그인 고유 식별자 |
getName() | 플러그인 이름 |
boot() | 부팅 시 실행 로직 |
register() | 서비스 등록 로직 |
선택 구현 메서드
| 메서드 | 기본값 | 설명 |
|---|---|---|
getVersion() | '1.0.0' | 버전 |
getDescription() | '' | 설명 |
getTier() | PluginTier::L0 | 티어 |
getPriceModel() | PriceModel::FREE | 가격 모델 |
getDependencies() | [] | 의존성 목록 |
install() | - | 설치 로직 |
uninstall() | - | 제거 로직 |
PluginManager
플러그인의 생명주기를 관리하는 서비스입니다.
주요 기능
// 플러그인 매니저 주입
use App\Core\Base\Plugin\Services\PluginManager;
class MyService
{
public function __construct(
private PluginManager $pluginManager
) {}
public function example(): void
{
// 모든 플러그인 조회
$all = $this->pluginManager->all();
// 활성화된 플러그인만
$enabled = $this->pluginManager->enabled();
// 특정 플러그인 조회
$plugin = $this->pluginManager->get('password-policy');
// 플러그인 존재 확인
if ($this->pluginManager->has('password-policy')) {
// ...
}
// 활성화 여부 확인
if ($this->pluginManager->isEnabled('password-policy')) {
// ...
}
// 티어별 조회
$l0Plugins = $this->pluginManager->getByTier('L0');
}
}
생명주기 관리
// 플러그인 활성화
$pluginManager->enable('my-plugin');
// 플러그인 비활성화
$pluginManager->disable('my-plugin');
// 플러그인 설치
$pluginManager->install('my-plugin');
// 플러그인 제거
$pluginManager->uninstall('my-plugin');
// 의존성 확인
$missing = $pluginManager->checkDependencies('my-plugin');
자동 발견
// plugins 디렉토리에서 플러그인 자동 발견
$discovered = $pluginManager->discover();
// Returns: ['password-policy', 'two-factor-auth', ...]
설치 및 활성화
1. 플러그인 설치
# Plugin Store에서 다운로드
# 또는 packages/plugins/ 디렉토리에 복사
# Composer 오토로드 갱신
composer dump-autoload
2. 플러그인 발견
// AppServiceProvider에서
public function boot(): void
{
app(PluginManager::class)->discover();
}
3. 플러그인 활성화
// 코드에서
$pluginManager->enable('password-policy');
// 또는 config/core.php에서
'plugins' => [
'enabled' => [
'password-policy',
'two-factor-auth',
],
],
4. 마이그레이션 실행
php artisan migrate
테이블 프리픽스 규칙
플러그인 테이블은 명명 규칙을 따릅니다.
| 영역 | 프리픽스 | 예시 |
|---|---|---|
| Core | core_ | core_plugins |
| Extensions | ext_ | ext_impersonate_logs |
| Plugins | plg_{name}_ | plg_pwd_history |
예시: PasswordPolicy 테이블
Schema::create('plg_pwd_history', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained();
$table->string('password_hash');
$table->timestamp('created_at');
});
조건부 기능 사용
플러그인 활성화 여부에 따라 기능을 조건부로 사용합니다.
use App\Core\Base\Plugin\Services\PluginManager;
class AuthService
{
public function __construct(
private PluginManager $pluginManager
) {}
public function authenticate(array $credentials): bool
{
// 기본 인증
$authenticated = Auth::attempt($credentials);
// 2FA 플러그인 활성화 시
if ($authenticated && $this->pluginManager->isEnabled('two-factor-auth')) {
return $this->verify2FA();
}
return $authenticated;
}
}
Blade에서 조건부 표시
@if(app(PluginManager::class)->isEnabled('analytics'))
<x-analytics-dashboard />
@endif
플러그인 설정
각 플러그인은 자체 설정 파일을 가집니다.
// packages/plugins/PasswordPolicy/src/Config/password-policy.php
return [
'min_length' => 12,
'max_length' => 128,
'history_count' => 5,
'breached_check' => true,
];