구현 패턴 예제
📝 초안 (Draft)
이 문서는 검토 중입니다. 내용이 변경될 수 있습니다.
Multi-SaaS Kit에서 자주 사용되는 구현 패턴입니다.
개요
Multi-SaaS Kit은 Laravel과 Filament를 기반으로 재사용 가능한 구현 패턴을 제공합니다. 이 문서에서는 Core 모듈의 주요 Trait와 패턴을 활용하는 방법을 설명합니다.
핵심 패턴 분류
| 패턴 | 모듈 | 용도 |
|---|---|---|
| 테넌트 격리 | Core/Tenant | 데이터 자동 격리 |
| Level 0~6 계층 권한 | Core/Permission | 계층별 접근 제어 |
| 감사 로그 | Core/Audit | 변경 이력 추적 |
| Guardian 관계 | Extensions/Guardian | 보호자-피보호자 관계 |
| 사용자 전환 | Extensions/Impersonate | 관리자 대리 접속 |
1. 테넌트 격리 패턴
BelongsToTenant Trait
모델에 BelongsToTenant Trait를 적용하면 자동으로 테넌트 격리가 적용됩니다.
namespace App\Models;
use App\Core\Base\Tenant\Traits\BelongsToTenant;
use Illuminate\Database\Eloquent\Model;
class Product extends Model
{
use BelongsToTenant;
protected $fillable = ['name', 'price', 'description'];
}
자동 동작
// 자동으로 현재 테넌트의 데이터만 조회
$products = Product::all(); // WHERE tenant_id = {current_tenant_id}
// 생성 시 자동으로 tenant_id 설정
$product = Product::create([
'name' => '상품 A',
'price' => 10000,
]);
// tenant_id가 자동으로 설정됨
Migration 패턴
Schema::create('products', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->decimal('price', 10, 2);
// 테넌트 격리용 컬럼
$table->foreignId('tenant_id')->constrained()->onDelete('cascade');
$table->index('tenant_id'); // 성능을 위한 인덱스
$table->timestamps();
});
테넌트 스코프 우회 (Platform Admin)
// Platform Admin만 전체 데이터 조회 가능
if (auth()->user()->isPlatformAdmin()) {
$allProducts = Product::withoutGlobalScope(TenantScope::class)->get();
}
2. 권한 분류 (Level 0~6) 패턴
HasLevel Trait
사용자 모델에 HasLevel Trait를 적용하면 권한 분류 (Level 0~6) 검사가 가능합니다.
namespace App\Models;
use App\Core\Base\Permission\Traits\HasLevel;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable
{
use HasLevel;
protected $casts = [
'level' => 'integer',
];
}
권한 레벨 확인
use App\Core\Base\Permission\Enums\UserLevel;
// 특정 레벨인지 확인
if ($user->hasLevel(UserLevel::TENANT_ADMIN)) {
// Level 2 (Tenant Admin) 이상만 접근
}
// 정확히 해당 레벨인지 확인
if ($user->isLevel(UserLevel::ORGANIZATION_ADMIN)) {
// Level 3 (Organization Admin)만 접근
}
// 레벨 범위 확인
if ($user->hasLevelBetween(UserLevel::TENANT_ADMIN, UserLevel::TEAM_LEADER)) {
// Level 2~5 사이만 접근
}
Middleware 적용
// routes/web.php
Route::middleware(['auth', 'ensure.level:0,1,2'])->group(function () {
// Platform Admin(0), SaaS Admin(1), Tenant Admin(2)만 접근
Route::get('/admin', [AdminController::class, 'index']);
});
Route::middleware(['auth', 'ensure.level:2'])->group(function () {
// Level 2 이상만 접근
Route::resource('users', UserController::class);
});
Policy에서 권한 검사
namespace App\Policies;
use App\Models\User;
use App\Models\Order;
use App\Core\Base\Permission\Enums\UserLevel;
class OrderPolicy
{
public function viewAny(User $user): bool
{
// 인증된 사용자는 목록 조회 가능 (TenantScope 자동 적용)
return true;
}
public function view(User $user, Order $order): bool
{
// Platform Admin은 모든 주문 조회 가능
if ($user->isPlatformAdmin()) {
return true;
}
// 같은 테넌트만 조회 가능
return $user->tenant_id === $order->tenant_id;
}
public function create(User $user): bool
{
// Level 4 (Workspace Admin) 이상만 생성 가능
return $user->hasLevel(UserLevel::WORKSPACE_ADMIN);
}
public function update(User $user, Order $order): bool
{
// 소유자 또는 Level 3 이상 관리자만 수정 가능
return $user->id === $order->user_id
|| ($user->tenant_id === $order->tenant_id
&& $user->hasLevel(UserLevel::ORGANIZATION_ADMIN));
}
public function delete(User $user, Order $order): bool
{
// Level 2 (Tenant Admin) 이상만 삭제 가능
return $user->tenant_id === $order->tenant_id
&& $user->hasLevel(UserLevel::TENANT_ADMIN);
}
}
3. 감사 로그 패턴
Auditable Trait
모델의 모든 변경사항을 자동으로 기록합니다.
namespace App\Models;
use App\Core\Base\Audit\Traits\Auditable;
use Illuminate\Database\Eloquent\Model;
class SensitiveData extends Model
{
use Auditable;
protected $fillable = ['name', 'value', 'status'];
// 감사 로그에서 제외할 필드
protected array $auditExclude = ['updated_at'];
// 마스킹할 민감 필드
protected array $auditMask = ['value'];
}
자동 기록 내용
// 생성
$data = SensitiveData::create(['name' => 'Secret', 'value' => '비밀값']);
// audit_logs: action=created, old_values=null, new_values={name:'Secret', value:'***'}
// 수정
$data->update(['status' => 'active']);
// audit_logs: action=updated, old_values={status:'pending'}, new_values={status:'active'}
// 삭제
$data->delete();
// audit_logs: action=deleted, old_values={...}, new_values=null