Là một lập trình viên, chắc hẳn mỗi chúng ta đều không xa lạ với khái niệm Design Pattern. Đó là các mẫu thiết kế chuẩn, những khuôn mẫu cho các vấn đề chung trong thiết kế phần mềm. Trong bài viết này, mình sẽ giới thiệu một design pattern phổ biến - Pipeline Pattern và cách triển khai nó trong Laravel.
1. Pipeline Design Pattern là gì ?
Ta có một dữ liệu đầu vào, dữ liệu được xử lý qua các giai đoạn (stage). Đầu ra của giai đoạn trước chính là đầu vào của giai đoạn tiếp theo. Bạn có thể pipeline tương tự như dây chuyền sản xuất trong nhà máy, nơi mỗi hạng mục trong dây chuyền sản xuất được xây dựng theo từng giai đoạn. Vật phẩm được chuyển từ công đoạn sản xuất này sang công đoạn sản xuất khác. Như vậy, Pipeline design pattern là một design pattern cung cấp khả năng xây dựng và thực thi một chuỗi các hành động theo từng bước. Các quy trình phức tạp có thể phân rã ra thành các nhiệm vụ đơn lẻ. Mỗi nhiệm vụ đơn lẻ lại có tính tái sử dụng cao. Chia nhỏ một quy trình lớn thành các tác vụ nhỏ hơn để xử lý dữ liệu và sau đó chuyển dữ liệu đó đến bước tiếp theo cho đến khi nhận được kết quả mong muốn.
Ý tưởng của Pipeline nhằm mục đích cho phép chúng ta xử lý dữ liệu trong một chuỗi các bước thực thi bằng một đầu vào thông qua các công đoạn và đầu ra sẽ được sử dụng cho bước tiếp theo. Trong đó, mỗi công đoạn xử lý sẽ trả về kết quả thành công hoặc thất bại. Trong trường hợp một trong các bước thất bại cả chuỗi xử lý sẽ dừng hoàn toàn. Trong trường hợp bước xử lý thành công, đầu ra của bước xử lý đó sẽ được sử dụng làm đầu vào cho công đoạn tiếp theo tới khi toàn bộ các bước đều được thực hiện theo đúng quy trình.
Laravel đã xây dựng sẵn khung Pipeline giúp chúng ta có thể dễ dàng ứng dụng design pattern này trong thực tế. Khi tìm hiểu về request lifecycle của Laravel, ta thấy Laravel cũng áp dụng Pipeline để cài đặt middleware nằm giữa Request và Response. Middleware cung cấp một cơ chế thuận tiện để lọc hoặc kiểm tra các yêu cầu HTTP trước khi các yêu cầu đó được chuyển tới controller. Trong class Illuminate\Foundation\Http của Laravel, ta sẽ bắt gặp phương thức sau:
/** * The application's global HTTP middleware stack. * * These middleware are run during every request to your application. * * @var array<int, class-string|string> */ protected $middleware = [ // \App\Http\Middleware\TrustHosts::class, \App\Http\Middleware\TrustProxies::class, \Illuminate\Http\Middleware\HandleCors::class, \App\Http\Middleware\PreventRequestsDuringMaintenance::class, \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class, \App\Http\Middleware\TrimStrings::class, \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class, ]; /** * Send the given request through the middleware / router. * * @param \Illuminate\Http\Request $request * @return \Illuminate\Http\Response */ protected function sendRequestThroughRouter($request) { $this->app->instance('request', $request); Facade::clearResolvedInstance('request'); $this->bootstrap(); return (new Pipeline($this->app)) ->send($request) ->through($this->app->shouldSkipMiddleware() ? [] : $this->middleware) ->then($this->dispatchToRouter()); }
Có thể thấy, Request để đi vào được ứng dụng phải đi qua 1 Pipeline, bao gồm các middleware như HandleCors, PreventRequestsDuringMaintenance, ValidatePostSize, TrimStrings, ConvertEmptyStringsToNull, các route middleware, controller middleware và cuối cùng mới dispatch đến router để nhận về một Response.
3. Ví dụ minh họa
Một trong những usecase phổ biến mà ta nghĩ ngay đến việc áp dụng Pipeline Pattern chính là chức năng lọc để thực hiện tìm kiếm.
Bài toán: Xây dựng một hệ thống ATS quản trị tuyển dụng có chức năng tìm kiếm ứng viên. Chức năng này cho phép chuyển viên tuyển dụng tìm kiếm các ứng viên theo nhiều tiêu chí khác nhau (họ tên, địa chỉ, giới tính...) Tạo một abstract class cho các Filter:
<?php namespace App\Filters; use Closure;
use Illuminate\Database\Eloquent\Builder; abstract class Filter
{ public function handle($request, Closure $next) { if (!request()->has($this->filterName())) { return $next($request); } return $this->applyFilter($next($request)); } protected function filterName() { return Str::snake(class_basename($this)); } /** * @param $filter * @param Builder $builder * @return Builder */ abstract protected function applyFilter($filter, Builder $builder): Builder;
}
Tạo các class Filter cho mỗi bước lọc ứng viên. Ví dụ class Name:
<?php namespace App\Filters; use Illuminate\Database\Eloquent\Builder; class Name extends Filter
{ /** * @param $filter * @param Builder $builder * @return Builder */ protected function applyFilter(Builder $builder): Builder { return $builder->where('name', 'like', '%' . request($this->filterName) . '%'); }
}
Sử dụng trong controller hoặc repository:
use Illuminate\Pipeline\Pipeline;
use App\Filters\Name;
use App\Filters\Address;
use App\Filters\Gender; $builder = Candidate::query();
$data = app(Pipeline::class) ->send($builder) ->through([ Name::class, Address::class, Gender::class, ]) ->thenReturn(); return $data->get();