TOML là gì? Ngôn ngữ Cấu hình Đã Vượt Qua YAML
TOML Thực Sự Là Gì
TOML là viết tắt của Tom's Obvious, Minimal Language. Người tạo ra nó, đồng sáng lập GitHub Tom Preston-Werner, đã quá chán nản với các định dạng cấu hình hiện có vào năm 2013. Ngôn ngữ này đã được phát triển và hoàn thiện trong gần tám năm trước khi bản phát hành ổn định đầu tiên, TOML v1.0.0, ra mắt vào tháng 1 năm 2021. Khoảng thời gian tinh chỉnh dài như vậy cho thấy thiết kế của nó đã được cân nhắc kỹ lưỡng đến mức nào. Về bản chất, TOML là một định dạng file cấu hình. Nó không phải là một định dạng tuần tự hóa dữ liệu đa năng như JSON, cũng không phải là một ngôn ngữ đánh dấu. Mục tiêu duy nhất là làm cho con người dễ dàng đọc và viết, đồng thời ánh xạ rõ ràng tới một bảng băm—hay cái mà bạn có thể gọi là dictionary, map, hoặc object trong ngôn ngữ lập trình của mình. Một file TOML tối thiểu cực kỳ đơn giản: ``` title = "My Application" version = "2.4.1" debug = false [database] host = "localhost" port = 5432 max_connections = 100 ``` Mỗi giá trị đều có một kiểu dữ liệu rõ ràng. `"localhost"` là một chuỗi (string). `5432` là một số nguyên (integer). `false` là một giá trị boolean—không phải chuỗi `"false"`, không phải `0`, và không phải `null`. Sự nghiêm ngặt này chính là điểm mấu chốt, và nó là lý do chính mà các lập trình viên chọn TOML. Bạn sẽ không bao giờ phải lãng phí thời gian tự hỏi liệu số cổng của mình sẽ được phân tích cú pháp (parse) thành một chuỗi hay một số nguyên chỉ vì một lỗi nhỏ trong một thư viện YAML cụ thể nào đó.
Tại Sao YAML Trở Thành Một Vấn Đề Đáng Được Giải Quyết
YAML được đánh giá cao vì tính thân thiện với con người. Đối với các file nhỏ, điều đó đúng. Nhưng sự thân thiện của nó nhanh chóng biến mất khi cấu hình của bạn lớn dần, và những lựa chọn thiết kế của nó bắt đầu gây ra rắc rối. Ví dụ tai tiếng nhất là Vấn đề Na Uy (Norway Problem). Trong YAML 1.1—vẫn là mặc định cho nhiều trình phân tích cú pháp (parser)—mã quốc gia hai chữ cái `NO` được phân tích cú pháp thành giá trị boolean `false`. Một cấu hình như `country: NO` sẽ âm thầm làm hỏng dữ liệu của bạn. Điều này cũng áp dụng cho `yes`, `no`, `on`, `off`, `true`, và `false` với nhiều kiểu viết hoa khác nhau. YAML 1.2 đã khắc phục lỗi này, nhưng bạn không phải lúc nào cũng có thể kiểm soát được phiên bản parser mà các công cụ của mình đang sử dụng. Sau đó là vấn đề về khoảng trắng (whitespace) quan trọng. YAML sử dụng thụt lề (indentation) để định nghĩa cấu trúc, vì vậy chỉ một khoảng trắng đặt sai chỗ cũng có thể âm thầm thay đổi toàn bộ ý nghĩa của file hoặc làm hỏng nó hoàn toàn. Bất kỳ ai từng mất hàng giờ để debug một pipeline CI/CD chỉ để phát hiện ra sự không nhất quán giữa hai khoảng trắng và bốn khoảng trắng đều hiểu rõ nỗi đau này. YAML cũng cung cấp quá nhiều cách để viết cùng một thứ. Các giá trị scalar có thể là dạng plain, single-quoted, double-quoted, hoặc block style. Các chuỗi (sequences) có thể là flow style hoặc block style. Sự linh hoạt này nghe có vẻ hay, nhưng nó có nghĩa là hai lập trình viên sẽ viết cùng một cấu hình logic theo những cách hoàn toàn khác nhau, khiến việc đọc diff trở nên khó khăn hơn và các buổi code review kém hiệu quả hơn. Điều này không làm cho YAML trở nên vô dụng. Nó là tiêu chuẩn cho các Kubernetes manifests và GitHub Actions workflows vì một lý do nào đó. Nhưng đối với cấu hình ứng dụng, nơi mà tính chính xác và khả năng dự đoán quan trọng hơn bất cứ điều gì, những lỗi vặt này là một điểm yếu nghiêm trọng.
TOML Giải Quyết Vấn Đề Dễ Đọc Như Thế Nào
Triết lý của TOML rất đơn giản: chỉ nên có một, và chỉ một, cách rõ ràng để viết một cái gì đó. Nghe có vẻ hạn chế, nhưng kết quả là các file cấu hình trông và cảm giác giống nhau, bất kể ai đã viết chúng hay chúng thuộc về dự án nào. Nó cung cấp sáu kiểu dữ liệu scalar: chuỗi (string), số nguyên (integer), số thực (float), boolean, offset date-time, và local date. Hỗ trợ hạng nhất của TOML cho ngày và giờ theo định dạng RFC 3339 là một tính năng cực kỳ hữu ích. Bạn có thể viết `created_at = 2024-03-15T09:30:00Z` và tin tưởng rằng nó sẽ trở thành một đối tượng datetime thích hợp, chứ không phải một chuỗi mà bạn phải tự phân tích cú pháp. Mặc dù YAML có thể biểu diễn ngày tháng, nhưng hành vi của nó không nhất quán giữa các parser khác nhau. Cấu trúc được định nghĩa bằng các bảng (sections) và mảng các bảng (arrays of tables). Một bảng có tiêu đề trong dấu ngoặc vuông: `[server]`. Một mảng các bảng sử dụng dấu ngoặc vuông kép: `[[products]]`. Cú pháp rõ ràng và dễ nhận biết. Dưới đây là một ví dụ thực tế từ một file `Cargo.toml` của Rust, cho thấy cách định nghĩa các dependency: ``` [dependencies] serde = { version = "1.0", features = ["derive"] } tokio = { version = "1", features = ["full"] } reqwest = "0.12" ``` Cú pháp bảng nội tuyến (inline table)—phần trong dấu ngoặc nhọn—rất tuyệt vời cho các định nghĩa đơn giản, gọn gàng. Đối với dữ liệu lồng ghép phức tạp hơn, bạn sử dụng các tiêu đề bảng đầy đủ. Ngôn ngữ này cung cấp cho bạn các quy tắc rõ ràng về thời điểm sử dụng từng kiểu. Ngay cả các comment cũng được thiết kế để dễ làm quen. Chúng sử dụng ký tự `#`, giống như Python và các shell script. Hầu hết các lập trình viên đã biết cú pháp này mà không cần đọc một dòng nào trong tài liệu đặc tả.
TOML Đã Thành Công Ở Đâu: Con Số Áp Dụng Thực Tế
Hệ sinh thái Rust là câu chuyện thành công lớn nhất của TOML. Cargo, trình quản lý gói của Rust, yêu cầu sử dụng TOML cho các manifest của nó. Với hơn 150.000 gói trên crates.io tính đến đầu năm 2025, mỗi gói đều có một file `Cargo.toml`. Đó là một thử nghiệm thực tế với quy mô lớn mà ít định dạng nào có thể vượt qua. Việc Python áp dụng thông qua PEP 518 (2016) và PEP 621 (2021) đã củng cố `pyproject.toml` trở thành nơi duy nhất và đúng đắn cho metadata dự án và cấu hình build. Các công cụ như Poetry, Hatch, Flit và PDM đều sử dụng nó. Cài đặt linter của bạn nằm trong `[tool.ruff]`, cài đặt kiểm thử của bạn trong `[tool.pytest.ini_options]`. Bạn có một file và một định dạng để quản lý tất cả. Hugo, trình tạo trang web tĩnh phổ biến, đã biến TOML thành định dạng cấu hình mặc định của mình, rời bỏ YAML và JSON. Nhóm phát triển đã đặc biệt trích dẫn tính rõ ràng của TOML và việc không có các ép kiểu (type coercion) bất ngờ là động lực. Khi một ngôn ngữ thêm một parser vào thư viện chuẩn của nó, bạn biết rằng định dạng đó đã thực sự được chấp nhận. Python đã làm điều đó bằng cách thêm `tomllib` vào phiên bản 3.11 (phát hành tháng 10 năm 2022), cho phép bạn phân tích cú pháp các file TOML trên bất kỳ bản cài đặt Python hiện đại nào mà không cần phụ thuộc bên ngoài. Nó cũng không chỉ là chuyện của Python và Rust. Go, .NET và JavaScript đều có các thư viện TOML trưởng thành, được duy trì tốt. Ví dụ, gói `@iarna/toml` trên npm có hàng triệu lượt tải xuống hàng tuần. TOML chính thức trở thành xu hướng chính.
Những Hạn Chế Thực Sự Của TOML
Không có công cụ nào hoàn hảo, và TOML cũng không ngoại lệ. Điều quan trọng là phải thành thật về những hạn chế của nó để bạn biết khi nào nên tìm đến một công cụ khác. Dữ liệu lồng ghép sâu là gót chân Achilles của TOML. Nếu bạn cần nhiều hơn hai hoặc ba cấp độ lồng ghép, cú pháp sẽ trở nên khó chịu. Bạn sẽ thấy mình phải viết các khóa dài, có dấu chấm như `[servers.production.database.replica]`. Nó hợp lệ, nhưng không dễ đọc. JSON và thậm chí YAML đơn giản là tốt hơn ở điểm này vì chúng được xây dựng để biểu diễn dữ liệu tổng quát. Các mảng lớn chứa các đối tượng phức tạp là một điểm yếu khác. Cú pháp `[[products]]` cho một mảng các bảng có nghĩa là bạn phải lặp lại tiêu đề đó cho mỗi mục. Một danh sách 50 sản phẩm sẽ dẫn đến 50 tiêu đề `[[products]]` riêng biệt. Đến lúc đó, bạn không còn viết một file cấu hình nữa; bạn đang sử dụng sai công cụ. Bạn nên sử dụng JSON hoặc một cơ sở dữ liệu. TOML thiếu hỗ trợ cho anchors và aliases, một tính năng mà YAML cung cấp với `&anchor` và `*alias`. Điều này có nghĩa là bạn không thể định nghĩa một khối cài đặt một lần và tái sử dụng nó trong toàn bộ file của mình. Nếu bạn cần lặp lại cấu hình, bạn có hai lựa chọn: sao chép trực tiếp, hoặc xây dựng logic hợp nhất vào code ứng dụng của bạn. Không có cách tích hợp sẵn nào để giữ cho nó DRY (Don't Repeat Yourself). Cuối cùng, bạn không thể truyền phát (stream) một file TOML. Không giống như JSON Lines, một tài liệu TOML phải được đọc và phân tích cú pháp toàn bộ trước khi bạn có thể truy cập bất kỳ giá trị nào. Điều này có thể quan trọng đối với các file cấu hình khổng lồ, mặc dù một file cấu hình lớn như vậy thường là dấu hiệu của một vấn đề thiết kế sâu sắc hơn. Những hạn chế này không làm cho TOML trở thành một lựa chọn tồi cho mục đích sử dụng dự kiến của nó: cấu hình ứng dụng có độ phức tạp vừa phải. Chúng chỉ định nghĩa ranh giới của nó.
Chuyển Đổi Giữa TOML Và Các Định Dạng Khác
Nếu bạn đang di chuyển một dự án từ YAML hoặc chỉ cần chuyển đổi dữ liệu cấu hình giữa các định dạng cho một công cụ, bạn có một vài lựa chọn. Để chuyển đổi bằng lập trình trong Python, bạn có thể kết hợp các thư viện `tomllib` (đọc) và `tomli-w` (ghi) với một trình phân tích cú pháp YAML như `PyYAML`. Đọc một file YAML vào một dictionary Python bằng `yaml.safe_load()` và sau đó ghi nó ra bằng `tomli_w.dumps()` hoạt động tốt, nhưng nó phù hợp nhất cho các file phẳng hoặc lồng ghép vừa phải. Các cấu trúc lồng ghép sâu, YAML anchors, hoặc các file đa tài liệu sẽ yêu cầu dọn dẹp thủ công. Đối với chuyển đổi từ JSON sang TOML, việc ánh xạ sạch sẽ hơn nhiều vì cả hai định dạng đều có kiểu dữ liệu rõ ràng. Một số nguyên JSON trở thành một số nguyên TOML. Một boolean JSON trở thành một boolean TOML. Sự thay đổi cấu trúc chính là biến các mảng đối tượng JSON thành các mảng bảng TOML (`[[table]]`), điều mà một công cụ chuyển đổi tốt có thể xử lý. Các công cụ trực tuyến như CocoConvert có thể xử lý việc chuyển đổi cho bạn. Tải lên một file JSON hoặc YAML, chọn TOML làm định dạng đầu ra, và công cụ chuyển đổi sẽ thực hiện công việc. Đây là một lựa chọn tuyệt vời cho các file cấu hình đơn giản. Đối với các file có các tính năng đặc thù của YAML như anchors hoặc cấu trúc lồng ghép sâu, bạn vẫn cần phải xem xét lại kết quả đầu ra. CocoConvert sẽ gắn cờ cảnh báo chuyển đổi khi nó tìm thấy điều gì đó không ánh xạ rõ ràng sang TOML, nhưng nó không thể tái cấu trúc cấu trúc dữ liệu cho bạn—đó là quyết định bạn phải tự đưa ra. Đối với các đợt di chuyển hàng loạt, chẳng hạn như chuyển đổi toàn bộ một kho lưu trữ gồm 40 file YAML, việc tải lên từng file một là không khả thi. API của CocoConvert được xây dựng cho mục đích này, cho phép bạn xử lý các yêu cầu theo lô và tự động hóa toàn bộ quá trình.
Bạn Có Nên Chuyển Dự Án Của Mình Sang TOML?
Vậy, bạn có nên chuyển đổi không? Câu trả lời phụ thuộc vào dự án của bạn và những gì bộ công cụ của nó mong đợi. Đối với một dự án Python mới vào năm 2025, câu trả lời là một tiếng "có" dứt khoát. Hãy sử dụng `pyproject.toml`. Hệ sinh thái đã chuẩn hóa nó, và việc chống lại điều này chỉ tạo ra ma sát không cần thiết. Đừng là người tạo ra một file `ruff.toml` riêng biệt khi tiêu chuẩn là sử dụng phần `[tool.ruff]` trong file cấu hình chính của dự án. Nếu bạn đang viết một dự án Rust, đây thậm chí không phải là một câu hỏi. Cargo sử dụng TOML, và đó là một tính năng, không phải một lỗi (bug). Tính nhất quán của định dạng là một phần lớn lý do tại sao các công cụ của Rust lại cảm thấy rất mạch lạc. Đối với một dự án hiện có với cấu hình YAML đang hoạt động, tôi sẽ nói chỉ nên di chuyển nếu YAML đó là nguồn gốc của sự khó chịu. Nếu `config.yaml` của bạn ổn định và nhóm phát triển hiểu rõ nó, chi phí chuyển đổi—cập nhật tài liệu, script và thói quen của mọi người—có lẽ không đáng để đổi lấy sự 'tinh khiết' của việc sử dụng TOML. Trong thế giới của Kubernetes và CI/CD (GitHub Actions, GitLab CI, v.v.), YAML là vua. Bạn không có lựa chọn nào khác, và TOML cũng không cố gắng soán ngôi nó trong lĩnh vực đó. Phép thử thực sự là đây: bạn có đang viết các comment trong file YAML của mình để giải thích kiểu dữ liệu mà một giá trị *nên* có không? Bạn có đang phải săn lùng các lỗi do một số bị đọc thành một chuỗi không? Đó chính là tín hiệu của bạn. Bạn đã vượt qua sự mơ hồ của YAML. TOML được thiết kế để giải quyết chính xác loại vấn đề này bằng cách làm cho các kiểu dữ liệu trở nên rõ ràng ngay từ đầu.