© 2026 Laravel

Kiến trúc & Design Patterns Trong Laravel

4 phút đọc 34 lượt xem

Ở level junior/middle, Laravel thường được dùng theo kiểu “Controller → Model → View”. Nhưng khi hệ thống lớn dần, cách tiếp cận này sẽ dẫn đến:

  • Code khó maintain
  • Logic bị dồn vào controller/model (fat controller, fat model)
  • Khó test
  • Khó scale

Vì vậy, kiến trúc là bước chuyển quan trọng nhất để lên Senior.

#I. Vấn đề thực tế

Fat Controller + Hidden Coupling

class OrderController extends Controller
{
    public function store(Request $request)
    {
        DB::beginTransaction();

        try {
            $user = auth()->user();

            $order = Order::create([
                'user_id' => $user->id,
                'total' => 0
            ]);

            $total = 0;

            foreach ($request->items as $item) {
                $product = Product::lockForUpdate()->findOrFail($item['id']);

                if ($product->stock < $item['qty']) {
                    throw new \Exception('Out of stock');
                }

                $product->decrement('stock', $item['qty']);

                $total += $product->price * $item['qty'];

                OrderItem::create([
                    'order_id' => $order->id,
                    'product_id' => $product->id,
                    'qty' => $item['qty']
                ]);
            }

            $order->update(['total' => $total]);

            DB::commit();

            return response()->json($order);
        } catch (\Throwable $e) {
            DB::rollBack();
            throw $e;
        }
    }
}

#Vấn đề nâng cao

  • Transaction logic nằm sai layer
  • Concurrency handling bị lộ
  • Business rule không reusable
  • Vi phạm SRP + DIP

#II. Service Layer Pattern

Refactor + Transaction + Domain logic

class OrderService
{
    public function __construct(private ProductService $productService) {}

    public function createOrder(User $user, array $items): Order
    {
        return DB::transaction(function () use ($user, $items) {
            $order = Order::create([
                'user_id' => $user->id,
                'total' => 0
            ]);

            $total = 0;

            foreach ($items as $item) {
                $product = $this->productService->getForUpdate($item['id']);

                $this->productService->decreaseStock($product, $item['qty']);

                $total += $product->price * $item['qty'];

                OrderItem::create([
                    'order_id' => $order->id,
                    'product_id' => $product->id,
                    'qty' => $item['qty']
                ]);
            }

            $order->update(['total' => $total]);

            return $order;
        });
    }
}

Best practices

  • Transaction nằm ở Service
  • Tách logic stock riêng
  • Có thể reuse / test độc lập

#III. Repository Pattern

Khi nào nên dùng?

  • Multiple data source (DB + API)
  • Query phức tạp
  • Cần mock khi test

Khi KHÔNG nên dùng

  • CRUD đơn giản
  • Chỉ dùng Eloquent

Advanced pitfall

  • Double abstraction (Repository + Service)
  • Mất lợi thế của Eloquent ORM

#IV. Action Pattern

class CreateOrderAction
{
    public function execute(User $user, array $items): Order
    {
        // chỉ 1 use case duy nhất
    }
}

So sánh Service vs Action

Tiêu chí Service Action
Scope Business domain Single use case
Test Medium Easy
Scale OK Very high

Thực tế: kết hợp cả 2

#V. DDD (Domain-Driven Design)

Concept nâng cao

  • Entity
  • Value Object
  • Aggregate Root
  • Domain Service

Ví dụ Value Object

class Money
{
    public function __construct(public int $amount) {}

    public function add(Money $money): self
    {
        return new self($this->amount + $money->amount);
    }
}

Khi dùng DDD?

  • Domain phức tạp
  • Nhiều rule
  • Team lớn

#VI. Clean Architecture (Laravel context)

Dependency Rule

  • Controller → UseCase → Domain
  • Infrastructure không ảnh hưởng Domain

Ví dụ interface

interface PaymentGateway
{
    public function charge(int $amount): bool;
}

#VII. Kinh nghiệm thực chiến

  • 70–80% project: Service + Eloquent là đủ
  • Action pattern rất hợp micro logic
  • Repository chỉ dùng khi cần abstraction thật sự
  • DDD = chi phí cao → chỉ dùng khi đáng
  • Clean Architecture giúp test cực tốt

#VIII. Interview Questions

1. Service Layer là gì?

Summary: Tách business logic khỏi controller

Detail:

  • Giảm coupling
  • Dễ test
  • Dễ maintain
  • Đặt transaction đúng nơi
2. Khi nào KHÔNG nên dùng Repository?

Summary: Khi Eloquent đã đủ

Detail:

  • CRUD đơn giản
  • Không cần abstraction
  • Tránh over-engineering
3. So sánh Service vs Action?

Summary: Service = nhiều logic, Action = 1 use case

Detail:

  • Action dễ test hơn
  • Service phù hợp orchestration
  • Kết hợp là tối ưu nhất
4. DDD có cần thiết không?

Summary: Không phải lúc nào cũng cần

Detail:

  • Small project → không cần
  • Complex domain → rất cần
5. Clean Architecture khác gì MVC?

Summary: MVC = structure, Clean = rule dependency

Detail:

  • MVC không enforce dependency
  • Clean kiểm soát direction dependency

#IX. Tổng kết

  • Biết khi nào dùng pattern
  • Tránh over-engineering
  • Hiểu trade-off
  • Thiết kế dựa trên context

Kiến trúc tốt = hệ thống sống lâu + scale được + dễ maintain