Communitygithub.com

라라벨 패턴

프로덕션 애플리케이션을 위한 라라벨 아키텍처 패턴(라우팅/컨트롤러, Eloquent ORM, 서비스 레이어, 큐, 이벤트, 캐싱, API 리소스). 라라벨 앱을 구축하거나 검토할 때 사용하세요.

라라벨 패턴란 무엇인가요?

이 스킬은 확장 가능하고 유지보수 가능한 라라벨 애플리케이션을 구축하기 위한 일관된 아키텍처 패턴과 모범 사례를 제공합니다. 컨트롤러, 서비스, 액션 간의 명확한 계층 경계를 중심으로 애플리케이션을 구조화하는 방법을 다룹니다. 주요 내용으로는 얇은 컨트롤러와 단일 책임 액션(예: CreateOrderAction)을 권장하고, 명시적 및 스코프 바운드 라우팅을 통한 예측 가능한 라우팅, 타입 적용을 위한 Eloquent 모델, 캐스트, 스코프에 대한 모범 사례가 포함됩니다. 또한 N+1 문제를 피하기 위한 즉시 로딩, 재사용 가능한 쿼리 객체를 통한 복잡한 필터링, 글로벌/로컬 스코프, 마이그레이션, 폼 요청(DTO 포함)을 통한 검증, 일관된 API 리소스와 페이지네이션, 트랜잭션, 큐, 이벤트, 캐싱 전략에 대해서도 다룹니다. 이 스킬은 Cursor 및 유사한 AI 코딩 도우미와 함께 사용하기에 적합하며, 라라벨 앱을 구축하거나 검토할 때 컨트롤러, Eloquent, 서비스 레이어, 큐, API 리소스 전반에 걸쳐 구조화된 지침을 제공합니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/laravel-patterns

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

라라벨 개발 패턴

확장 가능하고 유지보수 가능한 애플리케이션을 위한 프로덕션 수준의 라라벨 아키텍처 패턴입니다.

사용 시기

  • 라라벨 웹 애플리케이션 또는 API 구축
  • 컨트롤러, 서비스 및 도메인 로직 구조화
  • Eloquent 모델 및 관계 작업
  • 리소스 및 페이지네이션을 사용한 API 설계
  • 큐, 이벤트, 캐싱 및 백그라운드 작업 추가

작동 방식

  • 명확한 경계(컨트롤러 -> 서비스/액션 -> 모델)를 기준으로 앱을 구조화합니다.
  • 명시적 바인딩과 스코프 바인딩을 사용하여 라우팅을 예측 가능하게 유지하고, 접근 제어를 위해 권한 부여를 시행합니다.
  • 타입이 지정된 모델, 캐스트 및 스코프를 선호하여 도메인 로직의 일관성을 유지합니다.
  • I/O 집약적인 작업은 큐에 넣고 비용이 많이 드는 읽기는 캐시합니다.
  • 설정은 config/*에 중앙 집중화하고 환경을 명시적으로 유지합니다.

예제

프로젝트 구조

명확한 계층 경계(HTTP, 서비스/액션, 모델)를 갖춘 전통적인 라라벨 레이아웃을 사용합니다.

권장 레이아웃

app/
├── Actions/            # 단일 목적 유스 케이스
├── Console/
├── Events/
├── Exceptions/
├── Http/
│   ├── Controllers/
│   ├── Middleware/
│   ├── Requests/       # 폼 요청 검증
│   └── Resources/      # API 리소스
├── Jobs/
├── Models/
├── Policies/
├── Providers/
├── Services/           # 조정 도메인 서비스
└── Support/
config/
database/
├── factories/
├── migrations/
└── seeders/
resources/
├── views/
└── lang/
routes/
├── api.php
├── web.php
└── console.php

컨트롤러 -> 서비스 -> 액션

컨트롤러를 얇게 유지하세요. 오케스트레이션은 서비스에, 단일 목적 로직은 액션에 넣습니다.

final class CreateOrderAction
{
    public function __construct(private OrderRepository $orders) {}

    public function handle(CreateOrderData $data): Order
    {
        return $this->orders->create($data);
    }
}

final class OrdersController extends Controller
{
    public function __construct(private CreateOrderAction $createOrder) {}

    public function store(StoreOrderRequest $request): JsonResponse
    {
        $order = $this->createOrder->handle($request->toDto());

        return response()->json([
            'success' => true,
            'data' => OrderResource::make($order),
            'error' => null,
            'meta' => null,
        ], 201);
    }
}

라우팅 및 컨트롤러

명확성을 위해 라우트-모델 바인딩과 리소스 컨트롤러를 선호합니다.

use Illuminate\Support\Facades\Route;

Route::middleware('auth:sanctum')->group(function () {
    Route::apiResource('projects', ProjectController::class);
});

라우트 모델 바인딩 (스코프)

스코프 바인딩을 사용하여 교차 테넌트 액세스를 방지합니다.

Route::scopeBindings()->group(function () {
    Route::get('/accounts/{account}/projects/{project}', [ProjectController::class, 'show']);
});

중첩 라우트 및 바인딩 이름

  • 이중 중첩(예: conversation vs conversations)을 피하기 위해 접두사와 경로를 일관되게 유지합니다.
  • 바인딩된 모델과 일치하는 단일 매개변수 이름을 사용합니다(예: Conversation의 경우 {conversation}).
  • 중첩 시 부모-자식 관계를 강제하기 위해 스코프 바인딩을 선호합니다.
use App\Http\Controllers\Api\ConversationController;
use App\Http\Controllers\Api\MessageController;
use Illuminate\Support\Facades\Route;

Route::middleware('auth:sanctum')->prefix('conversations')->group(function () {
    Route::post('/', [ConversationController::class, 'store'])->name('conversations.store');

    Route::scopeBindings()->group(function () {
        Route::get('/{conversation}', [ConversationController::class, 'show'])
            ->name('conversations.show');

        Route::post('/{conversation}/messages', [MessageController::class, 'store'])
            ->name('conversation-messages.store');

        Route::get('/{conversation}/messages/{message}', [MessageController::class, 'show'])
            ->name('conversation-messages.show');
    });
});

매개변수가 다른 모델 클래스로 해석되도록 하려면 명시적 바인딩을 정의합니다. 사용자 정의 바인딩 로직의 경우 Route::bind()를 사용하거나 모델에서 resolveRouteBinding()을 구현합니다.

use App\Models\AiConversation;
use Illuminate\Support\Facades\Route;

Route::model('conversation', AiConversation::class);

서비스 컨테이너 바인딩

명확한 의존성 연결을 위해 서비스 프로바이더에서 인터페이스를 구현체에 바인딩합니다.

use App\Repositories\EloquentOrderRepository;
use App\Repositories\OrderRepository;
use Illuminate\Support\ServiceProvider;

final class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->app->bind(OrderRepository::class, EloquentOrderRepository::class);
    }
}

Eloquent 모델 패턴

모델 설정

final class Project extends Model
{
    use HasFactory;

    protected $fillable = ['name', 'owner_id', 'status'];

    protected $casts = [
        'status' => ProjectStatus::class,
        'archived_at' => 'datetime',
    ];

    public function owner(): BelongsTo
    {
        return $this->belongsTo(User::class, 'owner_id');
    }

    public function scopeActive(Builder $query): Builder
    {
        return $query->whereNull('archived_at');
    }
}

사용자 정의 캐스트 및 값 객체

엄격한 타입 지정을 위해 열거형 또는 값 객체를 사용합니다.

use Illuminate\Database\Eloquent\Casts\Attribute;

protected $casts = [
    'status' => ProjectStatus::class,
];
protected function budgetCents(): Attribute
{
    return Attribute::make(
        get: fn (int $value) => Money::fromCents($value),
        set: fn (Money $money) => $money->toCents(),
    );
}

N+1 문제 방지를 위한 즉시 로딩

$orders = Order::query()
    ->with(['customer', 'items.product'])
    ->latest()
    ->paginate(25);

복잡한 필터링을 위한 쿼리 객체

final class ProjectQuery
{
    public function __construct(private Builder $query) {}

    public function ownedBy(int $userId): self
    {
        $query = clone $this->query;

        return new self($query->where('owner_id', $userId));
    }

    public function active(): self
    {
        $query = clone $this->query;

        return new self($query->whereNull('archived_at'));
    }

    public function builder(): Builder
    {
        return $this->query;
    }
}

글로벌 스코프 및 소프트 삭제

기본 필터링을 위해 글로벌 스코프를 사용하고 복구 가능한 레코드를 위해 SoftDeletes를 사용합니다. 계층적 동작을 의도하지 않는 한, 동일한 필터에 글로벌 스코프와 명명된 스코프를 모두 사용하지 마십시오.

use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Builder;

final class Project extends Model
{
    use SoftDeletes;

    protected static function booted(): void
    {
        static::addGlobalScope('active', function (Builder $builder): void {
            $builder->whereNull('archived_at');
        });
    }
}

재사용 가능한 필터를 위한 쿼리 스코프

use Illuminate\Database\Eloquent\Builder;

final class Project extends Model
{
    public function scopeOwnedBy(Builder $query, int $userId): Builder
    {
        return $query->where('owner_id', $userId);
    }
}

// 서비스, 리포지토리 등에서
$projects = Project::ownedBy($user->id)->get();

다단계 업데이트를 위한 트랜잭션

use Illuminate\Support\Facades\DB;

DB::transaction(function (): void {
    $order->update(['status' => 'paid']);
    $order->items()->update(['paid_at' => now()]);
});

마이그레이션

명명 규칙

  • 파일 이름은 타임스탬프를 사용합니다: YYYY_MM_DD_HHMMSS_create_users_table.php
  • 마이그레이션은 익명 클래스를 사용합니다(명명된 클래스 없음). 파일 이름이 의도를 전달합니다.
  • 테이블 이름은 기본적으로 snake_case 및 복수형입니다.

마이그레이션 예제

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    public function up(): void
    {
        Schema::create('orders', function (Blueprint $table): void {
            $table->id();
            $table->foreignId('customer_id')->constrained()->cascadeOnDelete();
            $table->string('status', 32)->index();
            $table->unsignedInteger('total_cents');
            $table->timestamps();
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('orders');
    }
};

폼 요청 및 검증

검증은 폼 요청에 유지하고 입력을 DTO로 변환합니다.

use App\Models\Order;

final class StoreOrderRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()?->can('create', Order::class) ?? false;
    }

    public function rules(): array
    {
        return [
            'customer_id' => ['required', 'integer', 'exists:customers,id'],
            'items' => ['required', 'array', 'min:1'],
            'items.*.sku' => ['required', 'string'],
            'items.*.quantity' => ['required', 'integer', 'min:1'],
        ];
    }

    public function toDto(): CreateOrderData
    {
        return new CreateOrderData(
            customerId: (int) $this->validated('customer_id'),
            items: $this->validated('items'),
        );
    }
}

API 리소스

리소스 및 페이지네이션을 사용하여 API 응답을 일관되게 유지합니다.

$projects = Project::query()->active()->paginate(25);

return response()->json([
    'success' => true,
    'data' => ProjectResource::collection($projects->items()),
    'error' => null,
    'meta' => [
        'page' => $projects->currentPage(),
        'per_page' => $projects->perPage(),
        'total' => $projects->total(),
    ],
]);

이벤트, 작업 및 큐

  • 부작용(이메일, 분석)을 위해 도메인 이벤트를 발생시킵니다.
  • 느린 작업(보고서, 내보내기, 웹훅)에는 큐에 작업을 사용합니다.
  • 재시도 및 백오프가 있는 멱등성 핸들러를 선호합니다.

캐싱

  • 읽기 집약적인 엔드포인트와 비용이 많이 드는 쿼리를 캐시합니다.
  • 모델 이벤트(created/updated/deleted) 시 캐시를 무효화합니다.
  • 손쉬운 무효화를 위해 관련 데이터를 캐싱할 때 태그를 사용합니다.

설정 및 환경

  • 비밀은 .env에, 설정은 config/*.php에 유지합니다.
  • 환경별 설정 재정의를 사용하고 프로덕션에서는 config:cache를 사용합니다.

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

관련 스킬