Skip to main content

구현 패턴 예제

📝 초안 (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

감사 로그 조회

use App\Core\Base\Audit\Models\AuditLog;

// 특정 모델의 변경 이력
$logs = AuditLog::where('auditable_type', SensitiveData::class)
->where('auditable_id', $data->id)
->orderBy('created_at', 'desc')
->get();

// 특정 사용자의 모든 활동
$userLogs = AuditLog::where('user_id', $userId)
->with('auditable')
->get();

// 특정 기간의 삭제 이력
$deletions = AuditLog::where('action', 'deleted')
->whereBetween('created_at', [$startDate, $endDate])
->get();

수동 감사 로그 기록

use App\Core\Base\Audit\Models\AuditLog;

// 커스텀 액션 기록
AuditLog::create([
'user_id' => auth()->id(),
'auditable_type' => User::class,
'auditable_id' => null, // 전체 대상
'action' => 'export',
'old_values' => null,
'new_values' => ['exported_count' => 100, 'format' => 'csv'],
'ip_address' => request()->ip(),
'user_agent' => request()->userAgent(),
]);

4. Guardian 관계 패턴

관계 설정

// 보호자 측 (예: 부모)
namespace App\Models;

use App\Core\Extensions\Guardian\Traits\IsGuardian;

class Guardian extends Model
{
use IsGuardian;
}

// 피보호자 측 (예: 학생)
namespace App\Models;

use App\Core\Extensions\Guardian\Traits\HasGuardians;

class Student extends Model
{
use HasGuardians;
}

Guardian 관계 생성

use App\Core\Extensions\Guardian\Enums\RelationshipType;
use App\Core\Extensions\Guardian\Services\GuardianService;

$guardianService = app(GuardianService::class);

// 관계 생성 요청
$relationship = $guardianService->createRelationship(
guardian: $parent,
ward: $student,
type: RelationshipType::PARENT
);
// 상태: pending (승인 대기)

Guardian 관계 승인/거부

// 승인 (관리자 또는 권한 있는 사용자)
$guardianService->approveRelationship($relationship, auth()->user());
// 상태: approved

// 거부
$guardianService->rejectRelationship($relationship, auth()->user(), '부적절한 관계');
// 상태: rejected

Guardian 권한 확인

use App\Core\Extensions\Guardian\Enums\RelationshipType;

// 특정 학생의 보호자인지 확인
if ($user->isGuardianOf($student, RelationshipType::PARENT)) {
// 부모로서 학생 정보 접근 가능
}

// 모든 유형의 보호자인지 확인
if ($user->isGuardianOf($student)) {
// 어떤 유형이든 보호자 관계가 있음
}

// 피보호자 목록 조회
$wards = $user->wards()
->where('status', 'approved')
->get();

5. Impersonate 패턴

사용자 전환 (Impersonation)

상위 관리자가 하위 사용자로 전환하여 문제 해결/지원을 제공합니다.

use App\Core\Extensions\Impersonate\Services\ImpersonateService;

$impersonateService = app(ImpersonateService::class);

// 전환 가능 여부 확인
if ($impersonateService->canImpersonate(auth()->user(), $targetUser)) {
// 사용자 전환
$impersonateService->impersonate(auth()->user(), $targetUser);

// 이제 auth()->user()는 $targetUser를 반환
}

원래 사용자로 복귀

// 전환 중인지 확인
if ($impersonateService->isImpersonating()) {
// 원래 사용자로 복귀
$impersonateService->stopImpersonating();
}

Impersonate 규칙

원래 레벨전환 가능 대상
Platform Admin (0)모든 사용자
SaaS Admin (1)Level 2-6 (같은 SaaS의 테넌트)
Tenant Admin (2)Level 3-6 (같은 테넌트)
Organization Admin (3)Level 4-6 (같은 조직)
Workspace Admin (4)Level 5-6 (같은 워크스페이스)
Team Leader (5)Level 6 (같은 팀)
Member (6)전환 불가

Filament에서 Impersonate 액션

use App\Core\Extensions\Impersonate\Filament\Actions\ImpersonateAction;

class UserResource extends Resource
{
public static function table(Table $table): Table
{
return $table
->columns([...])
->actions([
ImpersonateAction::make(), // 자동으로 권한 검사
]);
}
}

6. 복합 패턴 예제

테넌트 격리 + 권한 + 감사 로그

namespace App\Models;

use App\Core\Base\Tenant\Traits\BelongsToTenant;
use App\Core\Base\Audit\Traits\Auditable;
use Illuminate\Database\Eloquent\Model;

class Contract extends Model
{
use BelongsToTenant; // 테넌트 격리
use Auditable; // 감사 로그

protected $fillable = ['title', 'content', 'status', 'amount'];

protected array $auditMask = ['content']; // 계약 내용 마스킹
}

Policy에서 복합 권한 검사

class ContractPolicy
{
public function update(User $user, Contract $contract): bool
{
// 1. Platform Admin은 항상 가능
if ($user->isPlatformAdmin()) {
return true;
}

// 2. 같은 테넌트 + Level 3 이상
if ($user->tenant_id !== $contract->tenant_id) {
return false;
}

return $user->hasLevel(UserLevel::ORGANIZATION_ADMIN);
}

public function approve(User $user, Contract $contract): bool
{
// 1. 같은 테넌트 + Level 2 이상
if ($user->tenant_id !== $contract->tenant_id) {
return false;
}

// 2. Guardian 관계 확인 (필요한 경우)
if ($contract->requires_guardian_approval) {
return $user->isGuardianOf($contract->owner);
}

return $user->hasLevel(UserLevel::TENANT_ADMIN);
}
}

관련 문서