+3

Imperative CLI: Tư Duy "Cầm Tay Chỉ Việc" & Sự Tiến Hóa Lên Declarative

Hồi mới tập tành làm DevOps kiêm Backend, mình từng viết một cái shell script (bash) cực kỳ tâm huyết để tự động hóa việc deploy con app Node.js lên server. Luồng chạy của nó đại khái như sau:

  1. git pull origin main (Kéo code mới)
  2. npm install (Cài thư viện)
  3. npm run build (Build code)
  4. pm2 restart app (Khởi động lại server)

Mọi thứ chạy mượt mà được nửa năm. Cho đến một ngày cá mập cắn cáp, bước npm install bị timeout và văng lỗi. Nhưng cái script ngốc nghếch của mình không có logic xử lý lỗi (error handling) chặt chẽ, nó vẫn nhắm mắt chạy tiếp bước 3 và bước 4. Kết quả? Con app trên Production bị restart với một đống code lỗi, sập toàn tập. Khách hàng réo tên mình lúc 12h đêm.

Đó chính là cái giá phải trả khi lạm dụng Imperative CLI (Giao diện dòng lệnh mang tính mệnh lệnh) cho những tác vụ hệ thống phức tạp.

1. Imperative CLI là gì? (Tư duy "Cầm tay chỉ việc")

Từ "Imperative" trong tiếng Anh mang nghĩa là "Bắt buộc, Mệnh lệnh". Một công cụ Imperative CLI yêu cầu bạn phải chỉ thị cho nó từng bước một, cực kỳ chi tiết về CÁCH (How) để đạt được kết quả.

Giống như việc bạn nhờ cậu em đi mua ly cà phê:

  1. "B1: Đi ra khỏi nhà."
  2. "B2: Rẽ trái, đi 500m."
  3. "B3: Vào quán, đưa 50k, bảo nhân viên pha 1 ly đen đá."
  4. "B4: Mang về đây."

Trong thế giới lập trình, các công cụ kinh điển như Git hay Docker (ở mức cơ bản) chính là Imperative CLI.

  • docker run -d -p 8080:80 nginx
  • docker stop my_nginx
  • docker rm my_nginx

Bạn viết ra tool bằng yargs ở bài trước? Khả năng cao nó cũng là Imperative. Bạn đang ra lệnh cho máy tính làm chính xác những gì bạn gõ

2. Nỗi đau của Imperative: Khi kịch bản gãy gánh

Tool Imperative rất dễ viết, dễ hiểu, nhưng nó chứa đựng 2 điểm yếu chí mạng khi mang lên môi trường Production (hoặc CI/CD):

  • Không có tính Lũy đẳng (Idempotency): Giả sử bạn gõ lệnh mkdir my_folder. Nó chạy ngon. Bạn lỡ tay gõ lại lệnh đó lần thứ 2, hệ thống chửi vào mặt bạn ngay: File exists. Imperative CLI không tự biết kiểm tra xem kết quả đã đạt được hay chưa. Bạn phải tự viết đống code if (folder_not_exists) then mkdir... cực kỳ mệt mỏi.
  • Bất lực khi Rollback: Nếu bạn có một kịch bản 10 bước (như ví dụ deploy ở đầu bài). Nó tạch ở bước 5. Lúc này hệ thống nằm ở trạng thái "lấp lửng" (nửa nạc nửa mỡ). Máy móc không biết làm sao để lùi lại 4 bước trước đó. Bạn phải tự tay SSH vào server để dọn rác.

3. Sự tiến hóa: Declarative CLI (Tư duy "Kết quả cuối cùng")

Để giải quyết nỗi đau trên, giới tinh hoa công nghệ đã tạo ra một tư duy mới: Declarative CLI (Khai báo).

Thay vì bảo máy móc CÁCH làm, bạn chỉ cần đưa cho nó một bản thiết kế mô tả KẾT QUẢ bạn muốn (What).

Quay lại ví dụ mua cà phê, tư duy Declarative là: "Trên bàn phải có 1 ly đen đá, tiền thừa 10k". Cậu em tự biết phải làm gì, đi đường nào, lỡ quán này đóng cửa thì sang quán khác.

Trong thực tế, Kubernetes (kubectl) và Terraform là hai trùm cuối của hệ phái này. Thay vì gõ 10 lệnh tạo server, bạn viết một file deployment.yaml:

# Tao muốn có 3 con server chạy Nginx phiên bản 1.21
replicas: 3
image: nginx:1.21

Sau đó bạn gõ một lệnh duy nhất: kubectl apply -f deployment.yaml

Lúc này, cái CLI engine bên dưới sẽ tự động so sánh trạng thái hiện tại của hệ thống với cái file YAML của bạn.

  • Nếu chưa có server nào, nó tự tạo 3 con.
  • Nếu đang có 1 con, nó tự tạo thêm 2 con.
  • Nếu đang có 5 con, nó tự "giết" bớt 2 con. Bạn gõ lệnh apply 100 lần, kết quả vẫn chỉ là 3 con server. Tính lũy đẳng (Idempotency) được giải quyết triệt để!

4. Vibe Coder chọn gì?

Có phải chúng ta nên vứt bỏ hết Imperative CLI? Không hề.

  • Dùng Imperative khi: Xử lý các tác vụ một lần (ad-hoc), debug lỗi, test logic cục bộ, hoặc viết các tool tự động hóa cá nhân (như tool dọn rác DB chúng ta viết bằng yargs). Nó nhanh, gọn, lẹ.
  • Dùng Declarative khi: Quản lý hạ tầng (Infrastructure as Code), hệ thống CI/CD, hoặc các stateful system phức tạp.

Một Vibe Coder thực thụ sẽ không cuồng tín một phe nào cả. Họ biết khi nào cần "cầm tay chỉ việc", và khi nào cần giao phó "bản thiết kế" cho hệ thống tự lo.

Chủ đề tiếp theo: Cơn Ác Mộng Của Node.js - Xử Lý File CSV 10GB Mà Không Bị OOM

Đã đến lúc thực hiện lời hứa từ 2 bài viết trước!

Bạn đã biết cách viết CLI Tool bằng yargs, bạn cũng biết cách thiết kế lệnh sao cho mượt. Bây giờ, Tech Lead giao cho bạn một file Báo cáo dữ liệu thô (CSV) nặng... 10GB. Yêu cầu viết một cái tool Node.js đọc file đó và insert vào Database.

Nếu bạn dùng lệnh fs.readFileSync('data.csv') (đây là một lệnh Imperative cực đoan), app Node.js của bạn sẽ nổ tung ngay lập tức vì 10GB data không thể nhét vừa 1.5GB RAM mặc định.

Ở bài viết tới, mình sẽ khai mở một "ma thuật" được ẩn giấu sâu bên trong kiến trúc Node.js: Node.js Streams. Làm sao để dòng chảy dữ liệu mượt mà đi từ File -> Node.js -> Database mà RAM Server vẫn chỉ nhịp nhàng ở mức 50MB? Anh em nhớ follow để đón xem nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí