Bỏ qua để đến nội dung

Mô hình pipeline

Spec: ISO 32000-2, §7.5 Evidence: Code-backed

Một tài liệu NextPDF không được tạo ra trong một bước mờ đục duy nhất. Tài liệu đi qua một vài giai đoạn rõ ràng: một facade ghi nhận ý định, một lớp nội dung biến ý định đó thành mô hình, và một writer chuyển mô hình ấy thành một PDF tuân thủ chuẩn. Trang này giải thích mô hình đó và lý do nó phù hợp với engine.

Bản thân định dạng tệp PDF là một cấu trúc phân lớp — một header, một phần thân chứa các đối tượng, một bảng tham chiếu chéo, và một trailer — và writer phải lắp ráp nhất quán tất cả những phần đó. Nếu engine chỉ tạo ra tệp bằng một thủ tục rối rắm, mỗi thay đổi đều có nguy cơ ảnh hưởng đến mọi đầu ra. Khi đó, cách duy nhất để tạo dựng niềm tin là kết xuất trọn vẹn tài liệu rồi kiểm tra bằng mắt, vốn chậm, muộn và thiếu thuyết phục.

Một pipeline rõ ràng đảo ngược điều đó. Mỗi giai đoạn có một nhiệm vụ và một ranh giới được định kiểu, nên bạn có thể suy luận về một thay đổi và kiểm thử ngay tại giai đoạn chịu tác động, thay vì chỉ kiểm tra ở phần cuối của tệp. Trước hết, kiến trúc này là một quyết định về khả năng kiểm thử và khả năng mở rộng.

  • Điểm vào công khai là một Document facade. Đây là một builder dạng fluent, dùng một lần, an toàn cho worker, ghi nhận điều gì bạn muốn, chứ không phải cách điều đó được chuyển hóa.
  • Facade ủy thác cho khoảng hai chục concern trait chuyên biệt (xuất văn bản, vẽ, trang, bảo mật, điều hướng, v.v.) — mỗi trait có một trách nhiệm, thay vì dồn vào một lớp khổng lồ.
  • Nội dung đi vào theo một trong hai đường: vẽ trực tiếp (các graphics primitive) hoặc engine HTML/CSS. Cả hai đều tạo ra cùng một document model nội bộ.
  • Một PDF writer chuyên trách chuyển mô hình đó, chọn một strategy PDF 1.4 / 1.7 / 2.0. Việc tạo cấu trúc tệp hợp lệ nằm ở đây và không ở nơi nào khác.
  • Trạng thái dài hạn (các registry phông chữ và hình ảnh) có phạm vi tiến trình và được dùng chung; trạng thái theo từng yêu cầu (tài liệu) được tạo mới và không bao giờ tái sử dụng. Ranh giới này được xác định rõ, và chính nó giúp các môi trường chạy worker an toàn.

Cách rõ ràng nhất để thấy mô hình này là theo dõi một tài liệu từ lúc được gọi cho đến khi trở thành các byte.

  1. Document facade Fluent, use-once builder; records intent via concern traits.
  2. Content production Direct drawing or the HTML/CSS engine — both build one document model.
  3. Document model Accumulated pages, content, and resources held as typed state.
  4. PDF writer Serialises the model; selects a PDF 1.4 / 1.7 / 2.0 strategy.
  5. Conforming PDF Header, object body, cross-reference table, trailer.
Đường đi của một tài liệu qua NextPDF: mỗi giai đoạn có một trách nhiệm và một ranh giới được định kiểu, nên nó có thể được suy luận và kiểm thử một cách độc lập.

Hai lựa chọn thiết kế khiến điều này không chỉ là một sơ đồ.

Facade được hợp thành, không phải nguyên khối. Document không tự mình hiện thực mọi tính năng; nó ủy thác từng mảng cho một concern trait chuyên trách — xuất văn bản, vẽ, trang, bảo mật, kiểu chữ, điều hướng, giao dịch, v.v. Một phương thức document mới thuộc về trait sở hữu mảng đó, thay vì nằm trực tiếp trên facade. Lớp mà bạn gọi vẫn nhỏ gọn, còn các trách nhiệm vẫn được tách biệt.

Writer độc quyền sở hữu cấu trúc tệp. Phần tạo nội dung quyết định những mark và đối tượng nào tồn tại; writer quyết định cách chúng trở thành một tệp PDF hợp lệ, bao gồm cả strategy phiên bản nào được áp dụng. Sự tách biệt đó được thực thi như một quy tắc kiến trúc: mã bố cục và nội dung không sinh cấu trúc tệp cuối cùng, còn writer không đưa ra quyết định về bố cục. Lợi ích là câu hỏi “đầu ra có phải là một PDF hợp lệ không?” chỉ có đúng một nơi để kiểm thử.

Ranh giới vòng đời là một phần của mô hình, không phải phần bổ sung về sau. Các registry phông chữ và hình ảnh tồn tại suốt vòng đời của tiến trình và được dùng chung giữa các yêu cầu; tài liệu, ngữ cảnh kết xuất của nó, và writer được tạo theo từng yêu cầu rồi hủy. Trong một môi trường chạy worker, sự phân biệt đó là ranh giới giữa tái sử dụng an toàn và lỗi dữ liệu giữa các yêu cầu. Vì vậy, nó được nêu rõ trong kiến trúc, thay vì phó mặc cho sự tự giác.

Trang này có Evidence: Code-backed . Các giai đoạn này ánh xạ tới cấu trúc thực tế trong kho mã core:

  • Facade và cơ chế ủy thác của nó là src/Core/Document.php cùng với các concern trait trong src/Core/Concerns/ (xuất văn bản, xuất, vẽ, trang, bảo mật, kiểu chữ, điều hướng, giao dịch, và nhiều mảng khác — mỗi trait một trách nhiệm duy nhất).
  • Hai đường nội dung là engine HTML/CSS (src/Html/) và vẽ trực tiếp (src/Graphics/), cả hai đều cấp dữ liệu cho mô hình nội bộ.
  • Việc chuyển hóa và strategy phiên bản PDF nằm trong src/Writer/ (PdfWriter.php, với các lớp strategy PDF 1.4 / 1.7 / 2.0 rõ ràng).
  • Ranh giới giữa vòng đời tiến trình và vòng đời theo từng yêu cầu là một phần của thiết kế an toàn cho worker được ghi lại trong phần tổng quan kiến trúc và được minh họa bằng ví dụ worker-factory đi kèm, vốn dùng chung một FontRegistryImageRegistry giữa các yêu cầu trong khi tạo mới mỗi Document.

Đích đến được cố định bởi định dạng. Đầu ra của writer phải là một header, một phần thân đối tượng, một bảng tham chiếu chéo, và một trailer theo Spec: ISO 32000-2, §7.5 . Tập trung nghĩa vụ đó vào một giai đoạn giúp phần còn lại của engine tiếp tục tập trung vào nội dung, thay vì việc lắp ráp cấu trúc tệp.

Nhiệm vụ của facade là làm cho ý định khi đọc lên vẫn đúng như ý định ban đầu. Đường nội dung và writer vẫn được ẩn khỏi nơi gọi:

<?php
declare(strict_types=1);
require_once __DIR__ . '/vendor/autoload.php';
use NextPDF\Core\Document;
$doc = Document::createStandalone(); // facade
$doc->setTitle('Quarterly Report'); // metadata concern
$doc->addPage(); // pages concern
$doc->setFont('helvetica', 'B', 16); // typography concern
$doc->cell(0, 12, 'Summary', newLine: true); // text-output concern
$doc->writeHtml('<p>Generated in-process.</p>'); // HTML content path
$doc->save(__DIR__ . '/report.pdf'); // writer stage

Mỗi lời gọi thuộc về một concern khác nhau. Hai đường nội dung khác nhau cùng cấp dữ liệu cho một mô hình. Chỉ một giai đoạn — save() — biến mô hình thành byte tệp. Không có gì ở nơi gọi cần biết bảng tham chiếu chéo được dựng như thế nào.

Cách hiểu sai thường gặp là “pipeline” ngụ ý một push API kiểu luồng, nơi bạn nối từng giai đoạn lại với nhau giống như một Unix pipe. Không phải vậy. Pipeline ở đây là một cách phân rã về mặt kiến trúc: các giai đoạn có trách nhiệm đơn lẻ và ranh giới được định kiểu. Bạn vẫn lập trình dựa trên một facade dạng fluent. Các giai đoạn là cách engine được xây dựng và kiểm thử, chứ không phải một kênh truyền mà bạn tự tay lắp ráp.

Một sai lầm liên quan là cho rằng facade chính là engine. Nó là điểm vào. Công việc thực sự được phân bổ qua các concern trait, hai đường nội dung, và một writer. Cách phân bổ đó chính là lý do một thay đổi tính năng không khiến mọi đầu ra gặp rủi ro.

Trang này mô tả mô hình của pipeline, không phải API nội bộ của bất kỳ giai đoạn đơn lẻ nào. Danh sách chính xác các concern trait, các quy tắc chọn strategy của writer, và các trường của content model được định nghĩa bởi mã nguồn và tài liệu tham chiếu, không phải bởi phần giải thích này. Số lượng trait chính xác là một chi tiết triển khai có thể thay đổi mà không làm thay đổi mô hình. Trang này không đề cập đến các giai đoạn nội bộ của engine HTML (một chủ đề riêng) hay hành vi luồng và bộ nhớ của writer (cũng là chủ đề riêng). Các tuyên bố về cấu trúc là chính xác tính đến ngày rà soát của trang này; nguồn có thẩm quyền là src/Core/, src/Html/, src/Graphics/, và src/Writer/ trong kho mã core.

Mô hình pipeline giống nhau trên mọi phiên bản; các phiên bản bổ sung năng lực bên trong các giai đoạn, chứ không thêm giai đoạn mới:

Pipeline model — edition availability
Edition Availability
Core Core hiện thực trọn vẹn pipeline facade → nội dung → writer.
Pro Pro bổ sung năng lực bên trong các giai đoạn hiện có, không thêm giai đoạn mới.
Enterprise Enterprise bổ sung năng lực bên trong các giai đoạn hiện có, không thêm giai đoạn mới.
  • Facade — điểm vào công khai Document: một builder dạng fluent, dùng một lần, ghi nhận ý định và ủy thác cho các concern trait.
  • Concern trait — một PHP trait chuyên biệt mà facade hợp thành, mỗi trait sở hữu một mảng tính năng duy nhất (xuất văn bản, vẽ, trang, bảo mật, v.v.).
  • Đường nội dung — một trong hai cách nội dung đi vào mô hình: vẽ trực tiếp hoặc engine HTML/CSS.
  • Document model — phần tích lũy nội bộ, được định kiểu của engine gồm các trang, nội dung và tài nguyên trước khi được chuyển hóa.
  • Giai đoạn writer — thành phần chuyển hóa mô hình thành một PDF hợp lệ, chọn một strategy PDF 1.4 / 1.7 / 2.0.
  • An toàn cho worker — được thiết kế sao cho trạng thái vòng đời tiến trình được dùng chung một cách an toàn trong khi trạng thái theo từng yêu cầu được tạo mới và không bao giờ tái sử dụng.