Chapter 2: Factor II

Factor II – Dependencies

Khai báo tường minh và cô lập toàn bộ dependencies

  • Theo Twelve-Factor, một app không được ngầm dựa vào các package cài sẵn toàn hệ thống; nó phải khai báo toàn bộ dependency một cách đầy đủ, chính xác trong manifest, và phải dùng công cụ cô lập dependency khi chạy để tránh package từ môi trường bên ngoài “lọt vào”. Việc này phải áp dụng thống nhất cho cả development lẫn production.
  • Tài liệu gốc nhấn mạnh: chỉ khai báo mà không cô lập, hoặc chỉ cô lập mà không khai báo, đều chưa đủ để đạt tinh thần Twelve-Factor.
  • Trong hệ sinh thái hiện đại, các công cụ chính thức như package.json + package-lock.json/npm ci của npm, venv + requirements file của Python, hay pom.xml của Maven đều đang giải quyết đúng bài toán này theo cách rất thực tế.

1) Mở bài: vì sao factor này đụng trúng “nỗi đau” của junior?

Đây là một cảnh rất quen: bạn clone dự án về, chạy lệnh cài thư viện, rồi lỗi.
Đồng nghiệp bảo: “À máy anh có sẵn package đó rồi”, hoặc “Em cài thêm curl, imagemagick, libpq, đúng version là được.” Chính từ kiểu phụ thuộc ngầm như vậy mà sinh ra câu nổi tiếng: “chạy trên máy em thì ổn”.

Twelve-Factor đặt Factor II rất sớm để dập từ gốc kiểu rủi ro này: dependency phải được khai báo rõ ràng và môi trường chạy phải được cô lập đủ tốt để app không vô tình dùng nhờ thứ gì đó từ máy host.

2) Định nghĩa gốc của Factor II

Tên chính thức của factor này là:

“Explicitly declare and isolate dependencies.”

Diễn giải đơn giản cho người mới:

App của bạn cần thư viện nào, công cụ nào, runtime nào thì phải ghi ra rõ ràng. Và khi app chạy, nó phải chạy trong một môi trường mà chỉ những thứ đã được khai báo mới được dùng.

Tài liệu gốc nói rất cụ thể:

  • Không được dựa vào sự tồn tại ngầm của package cài sẵn trên hệ thống.
  • Phải khai báo dependency “completely and exactly” qua một dependency manifest.
  • Phải dùng công cụ isolation khi thực thi để dependency từ môi trường ngoài không “leak in”.
  • Cách làm này phải áp dụng như nhau ở development và production.

3) Factor này đang giải quyết vấn đề gì?

Nó giải quyết 3 vấn đề rất thật:

a) Phụ thuộc ngầm

App chạy được chỉ vì máy của một người nào đó có sẵn package, binary hoặc tool. Khi sang máy khác thì vỡ. Đây là thứ Twelve-Factor cấm rất rõ.

b) Môi trường không tái tạo được

Khi không có manifest rõ ràng hoặc lock đủ chặt, hôm nay cài ra một cây dependency, tuần sau lại ra cây khác vì transitive dependency đã đổi. npm mô tả package-lock.json chính xác là file mô tả “exact tree” để các lần cài sau sinh ra cây dependency giống hệt nhau.

c) Setup cho người mới quá đau đớn

Tài liệu gốc xem đây là một lợi ích trực tiếp: developer mới chỉ cần checkout code, có language runtime và dependency manager là có thể setup app bằng một build/install command có tính deterministic.

4) “Khai báo” và “cô lập” là hai việc khác nhau

Nhiều bạn mới đọc hay nghĩ Factor II chỉ là “có file dependencies”. Chưa đủ.

Khai báo dependency

Là ghi rõ app cần gì trong manifest:

  • Node.js: package.jsondependencies, devDependencies, optionalDependencies, peerDependencies. npm docs nói rõ dependencies là map từ package name sang version range; còn các tool chỉ dùng lúc phát triển thì nên để ở devDependencies, không nên bỏ vào runtime dependencies.
  • Python: requirements file có thể chứa danh sách đầy đủ các package với pinned versions để đạt repeatable installations cho cả môi trường.
  • Java/Maven: pom.xml và cơ chế dependency management giúp định nghĩa và duy trì build tái lập với classpath và version rõ ràng.

Cô lập dependency

Là đảm bảo app chỉ nhìn thấy dependency của chính nó:

  • Python venv tạo virtual environment độc lập với base environment; chỉ các package cài trong môi trường đó mới khả dụng mặc định.
  • npm khuyến khích cây dependency của project được quản lý cục bộ bằng manifest và lockfile; với npm ci, môi trường CI/deploy sẽ cài sạch theo lockfile và không tự ý sửa manifest hay lock.

Và Twelve-Factor nói thẳng: chỉ làm một nửa là không đủ.

5) Ví dụ cực dễ hiểu

Hãy tưởng tượng bạn đi nấu ăn theo công thức.

  • Khai báo dependency là bạn ghi rõ nguyên liệu cần gì: 2 quả trứng, 200g bột, 1 hộp sữa.
  • Cô lập dependency là bạn nấu trong bếp của mình với đúng những nguyên liệu đó, không “mượn tạm” gia vị bí mật từ bếp hàng xóm mà không ghi vào công thức.

Nếu công thức của bạn thiếu dòng “cần bơ”, nhưng nhà bạn đang có sẵn bơ nên vẫn nấu được, thì lần sau người khác làm theo sẽ thất bại. Đó chính là lỗi phụ thuộc ngầm.

6) Ví dụ xuyên suốt với ứng dụng mẫu TaskFlow

Ta tiếp tục dùng app mẫu TaskFlow.

TaskFlow backend viết bằng Node.js, có:

  • Express
  • PostgreSQL driver
  • thư viện gửi email
  • test framework
  • ESLint

Cách làm đúng

  • express, pg, thư viện email để trong dependencies
  • eslint, jest để trong devDependencies
  • commit cả package.json lẫn package-lock.json
  • CI dùng npm ci thay vì npm install

Cách này bám đúng tinh thần Twelve-Factor: dependency khai báo rõ; môi trường build/deploy cài đúng cây dependency đã lock; không trông chờ package nào cài sẵn ở server. npm docs nói package-lock.json được commit vào source repo để teammates, deployments và CI cùng cài đúng một cây dependency; còn npm ci được thiết kế riêng cho automated environments như test, CI và deployment, với install “frozen” theo lockfile.

Cách làm sai

  • package.json thiếu một số package vì “máy dev nào cũng có rồi”
  • production server phải cài tay thêm imagemagick, curl, ffmpeg, hoặc thư viện hệ điều hành nhưng không ghi ở đâu
  • CI dùng npm install bừa, lockfile lệch với manifest vẫn không ai để ý

Twelve-Factor nói rất rõ rằng app cũng không nên dựa vào sự tồn tại ngầm của system tools như ImageMagick hay curl; nếu app cần shell out ra tool hệ thống, tool đó cần được “vendored” cùng app theo cách phù hợp.

7) Trường hợp thực tế đã áp dụng

Trường hợp 1: npm trong CI/CD

Đây là case rất thực dụng và đã thành chuẩn vận hành ở nhiều dự án Node.js. Tài liệu npm nói:

  • package-lock.json mô tả exact dependency tree để các lần cài sau có thể tạo ra cây giống hệt nhau.
  • File này được thiết kế để commit vào source repository.
  • npm ci dùng cho môi trường tự động như CI và deployment, yêu cầu lockfile phải tồn tại, sẽ báo lỗi nếu lockfile không khớp package.json, xóa node_modules cũ, và không ghi đè lên manifest hay lockfile.

Nói dễ hiểu: đây là ví dụ rất điển hình của việc khai báo + khóa + cài sạch để tránh “máy này một kiểu, máy kia một kiểu”.

Trường hợp 2: Python dùng venv và requirements

Tài liệu Python và PyPA đều khuyến nghị dùng virtual environment cho package bên thứ ba. venv tạo môi trường Python ảo tách biệt; .venv thường đặt ngay trong project và nên loại khỏi version control; khi environment đang active, pip sẽ cài package vào đúng môi trường đó. Đồng thời, requirements files thường được dùng để mô tả đầy đủ môi trường với pinned versions nhằm đạt repeatable installations.

Đây là một ví dụ thực tế rất sát với Twelve-Factor: manifest để khai báo, virtual environment để cô lập.

Trường hợp 3: Java với Maven

Maven mô tả dependency management là một tính năng cốt lõi, giúp tạo ra reproducible builds với classpath và version rõ ràng. Nó còn xử lý transitive dependencies và cho phép bạn khai báo explicit version để đảm bảo artifact được chọn đúng version mong muốn.

Đây là một ví dụ tốt để junior thấy rằng Factor II không phải “chỉ dành cho Node hay Python”, mà là nguyên lý chung.

8) Sai lầm phổ biến của junior

Hiểu sai 1: “Em có file manifest là xong”

Chưa xong. Twelve-Factor yêu cầu cả manifest lẫn isolation. Có requirements.txt mà vẫn cài package lung tung toàn máy, hoặc có package.json nhưng production lại trông chờ thư viện hệ thống cài tay, thì vẫn sai tinh thần factor này.

Hiểu sai 2: “Dependencies chỉ là thư viện code”

Không hẳn. Tài liệu gốc còn nhắc luôn đến system tools như ImageMagickcurl. Nếu app của bạn phụ thuộc vào chúng, đó cũng là dependency cần được xử lý tường minh.

Hiểu sai 3: “Lockfile không quan trọng”

Với Node.js, lockfile là thứ giúp cài ra đúng exact dependency tree và là thứ npm mong đợi bạn commit vào repo. Bỏ lockfile rất dễ khiến transitive dependency thay đổi âm thầm.

Hiểu sai 4: “Dev dependency muốn để đâu cũng được”

npm docs nói rõ test harnesses, transpilers và tool phát triển không nên bỏ vào runtime dependencies; chúng nên ở devDependencies.

9) Làm sai thì hậu quả gì?

  • Onboarding chậm vì ai vào cũng phải hỏi “máy cần cài thêm gì nữa?”
  • Bug khó tái hiện vì staging và local đang dùng cây dependency khác nhau.
  • Deploy dễ vỡ vì production thiếu một binary mà dev machine có sẵn.
  • Security patch khó kiểm soát vì không biết chính xác app đang dùng version nào của dependency.

Hai hậu quả đầu là suy ra trực tiếp từ yêu cầu deterministic setup và isolation của Twelve-Factor; còn khả năng tái lập của cây dependency và kiểm soát version là thứ được npm và Maven nhấn mạnh rất rõ trong tài liệu chính thức.

10) Áp dụng đúng trong team như thế nào?

Bước 1: Có manifest chuẩn cho từng app

Mỗi app cần một nơi khai báo dependency rõ ràng:

  • Node: package.json
  • Python: pyproject.toml hoặc requirements file tùy workflow
  • Java: pom.xml hoặc build file tương đương

npm docs, PyPA và Maven docs đều xem đây là nền tảng của việc quản lý dependency có kiểm soát.

Bước 2: Tách runtime deps và dev/test deps

Đừng trộn ESLint, Jest, migration helper, debug toolbar vào nhóm dependency runtime nếu app production không cần chúng để chạy. npm docs có phân tách rất rõ giữa dependenciesdevDependencies.

Bước 3: Dùng isolation thật sự

  • Python: python -m venv .venv
  • Node: cài dependency theo project, dựa trên manifest + lockfile
  • Java: build qua Maven/Gradle thay vì phụ thuộc jar cài tay ngoài hệ thống

Python docs còn khuyên .venv nên được loại khỏi version control và có thể xóa rồi dựng lại từ đầu.

Bước 4: Khóa dependency tree cho môi trường tự động

Với Node.js, commit package-lock.json và dùng npm ci trong CI/deploy là một thực hành rất hợp tinh thần Twelve-Factor.

Bước 5: Không dựa vào tool hệ thống “có sẵn”

Nếu app cần gọi ra curl, ffmpeg, imagemagick, SSL libs, database client binary hoặc tool CLI nào đó, hãy xử lý nó như dependency chính thức của app thay vì giả định máy nào cũng có. Tài liệu gốc gọi đích danh chuyện shell out ra system tools là thứ không nên dựa ngầm vào.

11) Diễn giải trong bối cảnh hiện đại

Đây là phần mở rộng hiện đại, không phải câu chữ gốc của Twelve-Factor.

Ngày nay, nhiều team dùng container image như một lớp isolation mạnh hơn. Docker docs mô tả container image là một gói chuẩn hóa chứa files, binaries, libraries và configuration cần để chạy container; với Python web app, image sẽ chứa cả Python runtime, app code và dependencies.

Vì vậy, trong bối cảnh hiện đại, ta có thể hiểu:

  • Manifest + lockfile giúp khai báo rõ
  • Virtualenv / local project install / build system giúp cô lập ở mức ngôn ngữ
  • Container image giúp đóng gói runtime environment ở mức hệ thống

Nhưng cần phân biệt rõ: Docker/container là cách diễn giải hiện đại của tư tưởng isolation, không phải nội dung gốc duy nhất của Factor II. Nội dung gốc vẫn là “explicitly declare and isolate dependencies.”

12) Checklist tự đánh giá

Hãy tự hỏi:

  • App của tôi có manifest dependency rõ ràng không?
  • Có dependency nào chỉ tồn tại vì máy dev hoặc server “đã cài sẵn” không?
  • Dev, CI và production có cùng cách cài dependency không?
  • Tôi có lock được dependency tree hoặc ít nhất kiểm soát version đủ chặt không?
  • Tool test/lint/build có bị trộn vào runtime dependencies không?
  • Nếu xóa sạch môi trường hiện tại, tôi có thể dựng lại app chỉ bằng codebase + manifest + install/build command không?

Nếu có từ 2 câu trở lên bạn chưa trả lời chắc chắn, Factor II của app đó đang khá yếu.

13) Tóm tắt nhanh

  • Factor II yêu cầu khai báo rõcô lập thật toàn bộ dependencies.
  • Không được phụ thuộc ngầm vào package hệ thống hay system tools cài sẵn.
  • Khai báo mà không cô lập thì vẫn có nguy cơ “leak in”; cô lập mà không manifest rõ thì người khác không dựng lại nổi.
  • Lockfile, virtual environment, build tool dependency management, và container image đều là những công cụ rất thực tế để hiện thực hóa tinh thần này.

14) 3 câu hỏi tự kiểm tra

  1. Vì sao Twelve-Factor nói chỉ khai báo dependency thôi là chưa đủ?
  2. package-lock.jsonnpm ci giúp gì cho CI/CD?
  3. Một binary hệ thống như curl hay ImageMagick có được xem là dependency của app không?

17) Nguồn tham khảo