Case study: Tách Laravel Monolith thành hệ Microservice

Anh ơi, Monolith tụi em chạy ngon, nhưng giờ đông khách, lag quá, tách microservice đi!
- Tách xong, cả team debug đến 2h sáng, chỉ muốn quay lại thời Route::get().

Ở case công ty TMĐT Việt Nam mà tôi đề cập ở các bài trước, dùng Laravel Monolith, xử lý 15k đơn hàng/ngày, có các module Auth, Order, Payment trong cùng codebase. Hệ thống “vẫn sống”, nhưng team 20 dev bắt đầu “đá nhau” vì code conflict, DB migration lộn xộn. Quyết định tách microservice, hành trình đầy “drama” bắt đầu!


Context ban đầu: Laravel Monolith “ổn nhưng khổ”

  • Hệ thống: Laravel 8, codebase ~500k dòng, shared MySQL DB.  
  • Module: Auth (JWT login), Order (đặt hàng), Payment (gọi API VNPay).  
  • Vận hành: Deploy trên EC2, CI/CD bằng GitLab CI, 1 lần deploy mất 15 phút.
Vấn đề gặp phải:  
  • Code conflict: Team Order và Payment cùng sửa OrderController, PR conflict liên tục.  
  • DB migration lộn xộn: Migration Auth làm hỏng schema của Order.  
  • Không scale riêng: Order cần 10 instance, nhưng Auth chỉ cần 2, cả app phải scale chung.

Quá trình tách: Từng bước “đau thương”

Bước 1: Tách Auth Service
  • Lý do: Auth ít phụ thuộc, chỉ cung cấp JWT và OAuth token.  
  • Thực hiện:  
    • Tách /app/Auth thành repo riêng, dùng Laravel + Redis cho session.  
    • Viết lại JWT logic, expose API /auth/login, /auth/validate.  
    • Order và Payment gọi API Auth qua REST.
  • Khó khăn: DB migration Auth phải tách table users sang MySQL riêng, đồng bộ user_id với Order/Payment.

Code mẫu: Auth Service API

// app/Http/Controllers/AuthController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;

class AuthController extends Controller {
    public function login(Request $request) {
        $token = auth()->attempt($request->only('email', 'password'));
        return response()->json(['token' => $token]);
    }
}

Bước 2: Dùng Laravel Octane để tạm scale
  • Lý do: Monolith vẫn nặng, cần cải thiện performance trước khi tách tiếp.  
  • Thực hiện: Cài Laravel Octane (Swoole), giảm response time từ 200ms xuống 50ms.  
  • Khó khăn: Một số middleware không tương thích, team phải refactor code.

Bước 3: Thay REST bằng gRPC
  • Lý do: REST latency cao khi Order gọi Auth liên tục (500ms/call).  
  • Thực hiện:  
    • Viết gRPC service cho Auth (dùng protobuf).  
    • Order Service gọi gRPC thay REST, latency giảm còn 100ms.
  • Khó khăn: Team mất 2 tuần học protobuf và gRPC setup.

Code mẫu: gRPC Protobuf

service AuthService {
  rpc ValidateToken (TokenRequest) returns (TokenResponse);
}
message TokenRequest { string token = 1; }
message TokenResponse { bool valid = 1; }

Đo lường kết quả

  • Build time: Giảm từ 15 phút (Monolith) xuống 5 phút/service.  
  • Deploy time: Mỗi service deploy độc lập, 2–3 phút.  
  • Velocity: Team Auth làm việc không block team Order, PR merge nhanh hơn 30%.  
  • Debug: Khó hơn, cần Jaeger để trace request qua Auth → Order.

Lessons learned

  • Đừng vội tách: Nếu Monolith chưa modular, tách microservice dễ thành “mớ bòng bong”.  
  • DB breaking change là bẫy: Tách table users làm schema Order/Payment lỗi, cần backup và test kỹ.  
  • gRPC không phải phép màu: Giảm latency nhưng tăng complexity, team cần học trước.

Góc nhìn CTO

Tách microservice là “phẫu thuật tim” cho hệ thống. Bắt đầu từ module ít phụ thuộc (như Auth), dùng công cụ tạm scale (Octane), và đầu tư tracing/logging từ sớm. Đừng tách chỉ vì “nghe xịn”, hãy tách khi Monolith thực sự “hắt hơi”.

Checklist tách microservice từ Monolith:  
  • Modular Monolith trước (tách boundary trong codebase).  
  • Tách module ít phụ thuộc (VD: Auth, Notification).  
  • Tách DB riêng, đồng bộ qua ID hoặc event.  
  • Dùng gRPC/Kafka nếu REST chậm, nhưng test kỹ.  
  • Thêm tracing (Jaeger) và logging (ELK/Loki).

🎯 Tóm lại: Tách Laravel Monolith thành microservice là hành trình từ Route::get() đến gRPC Gateway, đầy mồ hôi và nước mắt. Modular trước, tách DB cẩn thận, và đừng để prod thành “sân khấu drama”! full-width

Post a Comment

Mới hơn Cũ hơn