Đăng ký tài khoản: Đừng để cái Email Welcome đánh sập Server của bạn!
Lời mở đầu: Tội ác của hàm Mail::send()
Hãy nhìn vào một đoạn code Đăng ký kinh điển mà 90% anh em mới làm quen Laravel thường viết:
// ❌ BAD PRACTICE: Gộp tất cả vào Controller
public function register(Request $request)
{
// 1. Tạo user
$user = User::create([...]);
// 2. Gửi email
Mail::to($user->email)->send(new WelcomeEmail($user));
// 3. Trả kết quả
return response()->json(['message' => 'Thành công']);
}
Nhìn thì rất gọn, nhưng nó vi phạm nghiêm trọng về Hiệu năng. Hàm Mail::send() phải kết nối với SMTP Server (như Gmail, SendGrid, Mailgun) qua môi trường mạng (Network). Mạng thì luôn có độ trễ.
Khách hàng bấm "Đăng ký", màn hình loading quay đều 3 giây mới hiện thông báo thành công. Trải nghiệm cực kỳ tệ! Tồi tệ hơn, nếu server Mailgun lúc đó bị lỗi (Timeout), nguyên cái API đăng ký sẽ văng lỗi 500, dù data user thực ra đã được lưu vào DB rồi.
Hôm nay, chúng ta sẽ "giải phẫu" luồng này bằng kiến trúc Enterprise.
Bước 1: "Khiên chắn" Form Request
Đừng bao giờ validate dữ liệu trực tiếp trong Controller. Hãy đẩy nó ra FormRequest để Controller luôn sạch sẽ.
php artisan make:request RegisterUserRequest
// app/Http/Requests/RegisterUserRequest.php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class RegisterUserRequest extends FormRequest
{
public function authorize(): bool
{
return true;
}
public function rules(): array
{
return [
'name' => 'required|string|max:255',
'email' => 'required|string|email|max:255|unique:users',
'password' => 'required|string|min:8|confirmed',
];
}
}
Bước 2: Action Pattern và DB Transaction
Thay vì dùng Service ôm đồm nhiều thứ (như UserService chứa cả login, register, update), ta dùng Action Pattern (Mẫu thiết kế Hành động) để đảm bảo nguyên lý Single Responsibility (Đơn trách nhiệm). Mỗi class chỉ làm đúng 1 việc.
Đồng thời, khi đăng ký, ta thường phải tạo thêm profile rỗng, hoặc phân quyền rỗng. Hãy bọc nó trong DB::transaction để đảm bảo: Nếu lỗi ở đâu, rollback lại toàn bộ, không có chuyện user sinh ra mà mất profile.
# Laravel chưa có sẵn lệnh make:action, bạn tự tạo thư mục nhé
mkdir app/Actions
// app/Actions/RegisterUserAction.php
namespace App\Actions;
use App\Models\User;
use App\Models\UserProfile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
class RegisterUserAction
{
public function execute(array $data): User
{
return DB::transaction(function () use ($data) {
// 1. Tạo core User
$user = User::create([
'name' => $data['name'],
'email' => $data['email'],
'password' => Hash::make($data['password']),
]);
// 2. Tạo Profile đi kèm
UserProfile::create([
'user_id' => $user->id,
'avatar' => 'default.png',
]);
// 3. (Quan trọng) KHÔNG gửi email ở đây!
return $user;
});
}
}
Bước 3: Đánh thức hệ thống bằng Event & Queue (Bất đồng bộ)
Đây là "vũ khí hạng nặng" giúp API của bạn nhanh như chớp. Sau khi tạo user xong, hệ thống chỉ cần hét lên: "Ê, có một thằng mới đăng ký nè!" (Phát ra Event). Rồi nó kết thúc API luôn.
Việc gửi email sẽ do một thằng "Culi" đứng đằng sau (Queue Worker) âm thầm nhặt cái Event đó lên và đi gửi.
1. Tạo Event và Listener
php artisan make:event UserRegistered
php artisan make:listener SendWelcomeEmail --event=UserRegistered
2. File Event (Mang theo data)
// app/Events/UserRegistered.php
namespace App\Events;
use App\Models\User;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class UserRegistered
{
use Dispatchable, SerializesModels;
public User $user;
public function __construct(User $user)
{
$this->user = $user;
}
}
3. File Listener (Implement ShouldQueue để chạy ngầm)
Đây là mấu chốt, chỉ cần implements ShouldQueue, Laravel sẽ tự ném task này vào Redis hoặc RabbitMQ thay vì chạy đồng bộ.
// app/Listeners/SendWelcomeEmail.php
namespace App\Listeners;
use App\Events\UserRegistered;
use Illuminate\Contracts\Queue\ShouldQueue; // BẮT BUỘC PHẢI CÓ CÁI NÀY
use Illuminate\Support\Facades\Mail;
use App\Mail\WelcomeEmail;
class SendWelcomeEmail implements ShouldQueue
{
public function handle(UserRegistered $event): void
{
// Thằng Culi (Worker) sẽ từ từ chạy đoạn này ở background
Mail::to($event->user->email)->send(new WelcomeEmail($event->user));
}
}
(Nhớ đăng ký cặp Event-Listener này trong EventServiceProvider nhé).
Bước 4: Controller siêu mỏng (Thin Controller)
Nhìn lại cái Controller của chúng ta đi. Nó giờ chỉ đóng vai trò người điều phối, sạch sẽ đến bất ngờ:
// app/Http/Controllers/Api/AuthController.php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Http\Requests\RegisterUserRequest;
use App\Actions\RegisterUserAction;
use App\Events\UserRegistered;
class AuthController extends Controller
{
public function register(RegisterUserRequest $request, RegisterUserAction $action)
{
// 1. Dữ liệu đã được validate sạch sẽ từ Request
// 2. Gọi Action thực thi logic lõi
$user = $action->execute($request->validated());
// 3. Bắn Event ra cho hệ thống biết (Hệ thống Queue sẽ tự bắt lấy)
UserRegistered::dispatch($user);
// 4. Trả response ngay lập tức (Chỉ mất 50ms)
return response()->json([
'success' => true,
'message' => 'Đăng ký thành công! Vui lòng kiểm tra email.',
'data' => $user
], 201);
}
}
Bước 5: Thử lửa với Postman
Để chứng minh kiến trúc này "đỉnh" thế nào, hãy mở Postman lên.
(Lưu ý: Mở terminal chạy lệnh php artisan queue:work để bật thằng Culi xử lý hàng đợi lên nhé).
1. Setup Request Đăng ký:
- Method:
POST - URL:
[http://127.0.0.1:8000/api/register](http://127.0.0.1:8000/api/register) - Headers:
Accept:application/json - Body (raw - JSON):
{
"name": "Kỹ sư dởm",
"email": "kysudom@example.com",
"password": "Password123!",
"password_confirmation": "Password123!"
}
2. Kết quả:
- Response: Bắn về ngay lập tức mã
201 Createdcùng data user. - Nhìn vào Terminal Postman (Response Time): Bạn sẽ thấy con số thời gian phản hồi chỉ loanh quanh
40ms - 80ms. - Nhìn vào Terminal chạy Queue của Laravel: Tầm 1-2 giây sau, bạn mới thấy dòng chữ
Processing: App\Listeners\SendWelcomeEmailvàProcessedhiện lên.
Đó chính là sự kỳ diệu của Bất đồng bộ (Asynchronous). Người dùng không phải chờ cái Email được gửi xong. Hệ thống của bạn đã sẵn sàng hứng hàng ngàn request đăng ký mỗi giây mà không sợ tắc nghẽn luồng xử lý mạng.
Tóm lại
Một tính năng cơ bản như Đăng ký tài khoản, nếu áp dụng đúng tư duy hệ thống:
FormRequest: Bảo vệ cửa ngõ.Action + Transaction: Bảo vệ toàn vẹn dữ liệu Database.Event + Queue: Giải phóng thời gian chờ, tối ưu UX và System Performance.
Đây mới là cách code mà các tập đoàn lớn (Enterprise) kỳ vọng ở một kỹ sư Backend. Hãy lôi dự án cũ ra và refactor lại cái Controller đăng ký của anh em ngay đi!
All rights reserved