Go, hay còn gọi là Golang, nổi tiếng là ngôn ngữ mạnh mẽ, đơn giản và có mô hình xử lý đồng thời hiệu quả. Tuy nhiên, cộng đồng Go lại có một quan điểm truyền thống khá mạnh mẽ về việc *chống* lại các framework lớn và các quy ước tập trung. Đây chính là điểm mà nhiều đội ngũ, đặc biệt là những người chuyển sang từ các môi trường có cấu trúc sẵn như Rails, Django hay FastAPI, cảm thấy “đụng tường”.
Bài viết này sẽ đi sâu vào khoảng trống cấu trúc trong phát triển bằng Go, lý giải nguyên nhân tồn tại của nó và giới thiệu Encore.go – một framework dựa trên quy ước giúp lấp đầy khoảng trống đó, mang lại sự rõ ràng, nhất quán và năng suất cho phát triển backend.
Mục lục
Go: Mạnh Mẽ, Tối Giản và Không Quan điểm
Trích dẫn từ FAQ trên website chính thức của Go: “Không có hướng dẫn phong cách rõ ràng nào, mặc dù chắc chắn có một ‘phong cách Go’ dễ nhận biết.”
Ngay từ khi ra đời, Go đã được thiết kế để tránh sự phức tạp thường đi kèm với các framework lớn. Những người tạo ra nó đã cố tình bỏ qua nhiều thứ – cấu trúc dự án, định tuyến (routing), di chuyển cơ sở dữ liệu (migrations), xử lý tác vụ nền (background processing) – nhằm mang lại sự tự do và linh hoạt cho nhà phát triển.
Triết lý này mang lại những lợi ích thực sự. Sự đơn giản của Go giúp nó dễ học. Các cơ chế xử lý đồng thời (goroutines và channels) rất thanh lịch và hiệu quả. Và thư viện chuẩn của nó rất vững chắc và hiệu quả.
Nhưng như nhiều đội ngũ đã trải nghiệm trực tiếp: tính tối giản ở cấp độ ngôn ngữ không tự nhiên mở rộng được tới cấp độ đội nhóm.
Tại Sao Framework Lại Tồn Tại?
Các framework như Ruby on Rails hoặc Django tồn tại có lý do: chúng mã hóa các thực tiễn tốt nhất (best practices), giảm bớt mã lặp (boilerplate), và giúp các đội nhóm di chuyển nhanh hơn bằng cách đưa ra hàng nghìn quyết định thay cho bạn – các quyết định về đặt tên, cấu trúc thư mục, định tuyến, kiểm thử, và cách kết nối mọi thứ lại với nhau.
Chúng cung cấp thứ mà Go cố tình tránh: *các quan điểm (opinions)*.
Khi bạn sử dụng Rails hoặc Django, bạn có cấu trúc, cài đặt mặc định và công cụ tích hợp sẵn ngay khi bắt đầu. Khi bạn sử dụng Go, bạn có một trang giấy trắng.
Và trang giấy trắng đó có thể mang lại sự tự do – hoặc gây ra sự tê liệt.
Thách Thức Khi Go ‘Thả Rông’ Quá Nhiều
Đối với các nhà phát triển cá nhân hoặc các dự án nhỏ, việc Go thiếu cấu trúc có thể không phải là vấn đề. Nhưng đối với các đội nhóm xây dựng các hệ thống nghiêm túc, nó tạo ra ma sát và sự thiếu hiệu quả trên mọi phương diện.
Đây là những vấn đề mà các đội nhóm thường xuyên gặp phải:
Thiếu Chuẩn Mực Chung
Không có chuẩn mực chung nào cho bố cục dự án, cấu trúc tệp, ranh giới dịch vụ, hay thậm chí là cách viết các HTTP handler. Điều này dẫn đến:
- Tranh luận nội bộ về kiến trúc.
- Các dịch vụ “độc nhất vô nhị” với bố cục tùy chỉnh riêng.
- Chi phí đào tạo thành viên mới cao hơn vì mỗi dịch vụ lại khác nhau.
Quá Nhiều Quyết Định, Ít Hướng Dẫn
Trong Go, nhiều tác vụ phổ biến như tác vụ nền, di chuyển cơ sở dữ liệu và định tuyến được xử lý thông qua các thư viện của bên thứ ba thay vì công cụ tích hợp sẵn. Mặc dù hệ sinh thái cung cấp các tùy chọn chất lượng cao, nhưng không có một con đường “được Go chứng thực” hoặc một framework tích hợp duy nhất, vì vậy các đội nhóm thường dành thời gian nghiên cứu thư viện, kết nối chúng lại với nhau và duy trì tính nhất quán giữa các dịch vụ.
Hạ Tầng Tự Xây (DIY – Do It Yourself)
Vì văn hóa của Go cố tình chống lại framework, hầu hết các đội nhóm cuối cùng phải tự xây dựng mọi thứ. Điều này thường có nghĩa là dành thời gian phát triển quý báu cho các tác vụ không tạo ra giá trị sản phẩm độc đáo nào:
- Viết mã lặp để kết nối các dịch vụ và xử lý các vấn đề xuyên suốt như tracing (truy vết) và auth (xác thực).
- Quản lý các khóa bí mật (secrets) và cấu hình khác nhau trong mỗi môi trường.
- Tự lắp ráp hạ tầng theo cách thủ công bằng các công cụ như Terraform, Docker và các script CI/CD.
Đội Nhóm Dần Lệch Hướng
Nếu không có cấu trúc được thực thi, các codebase sẽ dần đi lệch hướng. Các đội nhóm làm việc trên các dịch vụ khác nhau sẽ phát triển các quy ước riêng của họ. Như một kỹ sư đã nói, “Mỗi dịch vụ Go cảm thấy như một thế giới nhỏ của riêng nó.”
Giải Pháp: Thêm Cấu Trúc Với Encore.go
Encore.go là một ví dụ về framework nhằm giúp các đội nhóm chuyển đổi sang Go mượt mà hơn.
Encore cung cấp cho các đội nhóm các quy ước, công cụ tích hợp sẵn và các cài đặt mặc định (defaults) cho cloud-native giúp loại bỏ mã lặp và giảm chi phí vận hành (DevOps) – nhờ đó các nhà phát triển có thể tập trung vào xây dựng sản phẩm, chứ không phải “ống nước” hạ tầng.
“Chúng tôi sẽ không bao giờ có thể xây dựng sản phẩm sách điện tử của mình trong thời gian đó mà không có Encore. Nó đã mang lại sự rõ ràng, công cụ và sự ổn định mà chúng tôi cần.”
— Mason Stewart, CTO tại Bookshop.org
Encore.go Mang Lại Gì?
- Chức năng tích hợp sẵn: Xác thực, định tuyến, di chuyển cơ sở dữ liệu, xử lý kết nối giữa các dịch vụ, v.v.
- Phát triển theo hướng quy ước: Các quy ước để định nghĩa API, dịch vụ, kiểm thử, tích hợp hạ tầng, v.v.
- Không cần cấu hình: Một lệnh duy nhất để khởi động môi trường cục bộ, bao gồm cả hạ tầng.
- Khả năng quan sát (Observability): Tracing, metrics và logging có cấu trúc được tích hợp mặc định và hiển thị trong bảng điều khiển phát triển của Encore.
- Thiết kế cho Cloud-Native: Hỗ trợ Docker hoặc tự động triển khai lên AWS/GCP thông qua Encore Cloud.
Thay vì bắt đầu từ đầu với Go “trần”, một số đội nhóm sử dụng Encore như một cách để áp dụng Go một cách dần dần – mà không từ bỏ cấu trúc mà họ đã quen thuộc.
Đây là cách nó có thể hoạt động trong thực tế:
Ví Dụ Mã
Định nghĩa Service
Encore nhận diện một package Go là một service. Khi chạy ứng dụng hoặc triển khai, nó tự động cấp phát hạ tầng cần thiết và tạo ra mã lặp kết nối.
/my-app
├── encore.app // ... và các tệp dự án cấp cao khác
│
├── hello // dịch vụ hello (một package Go)
│ ├── hello.go // mã dịch vụ hello
│ └── hello_test.go // kiểm thử cho dịch vụ hello
│
└── world // dịch vụ world (một package Go)
└── world.go // mã dịch vụ world
Xem thêm tài liệu về cấu trúc ứng dụng để biết chi tiết.
Định nghĩa API
Sử dụng chú thích //encore:api
, bạn định nghĩa một điểm cuối API bằng mã Go thuần túy:
package hello // tên service
import (
"context"
"fmt"
)
type PingParams struct {
Name string
}
type PingResponse struct {
Message string
}
//encore:api public
func Ping(ctx context.Context, params *PingParams) (*PingResponse, error) {
msg := fmt.Sprintf("Hello, %s!", params.Name)
return &PingResponse{Message: msg}, nil
}
Encore sau đó sẽ tự động tạo ra mã lặp và tài liệu cần thiết.
Tìm hiểu thêm trong tài liệu về định nghĩa API.
Chạy cục bộ
Chỉ cần chạy lệnh:
encore run
Encore sẽ tự động thiết lập hạ tầng cục bộ và một bảng điều khiển phát triển cục bộ với tài liệu API, biểu đồ kiến trúc, tracing và logs.
Triển khai Cloud
Sử dụng công cụ mã nguồn mở của Encore để xây dựng image Docker, hoặc lựa chọn Encore Cloud để tự động cấp phát hạ tầng và triển khai lên đám mây của bạn trên AWS hoặc GCP.
Ứng Dụng Thực Tế: Câu Chuyện Thành Công
Bookshop.org
Bookshop.org bắt đầu chuyển từ Rails sang Go để mở rộng quy mô tốt hơn. Nhưng đội ngũ nhanh chóng nhận ra tính tối giản của Go đi kèm với cái giá phải trả.
“Chúng tôi không có các quy ước mà chúng tôi đã quen thuộc, và điều đó tạo ra sự không nhất quán trong các ứng dụng của chúng tôi. Và trong khi Go tạo ra một binary duy nhất, vẫn có rất nhiều công việc liên quan đến Terraform và triển khai thủ công.”
— Mason Stewart, CTO tại Bookshop.org
Áp dụng Encore đã mang lại cấu trúc và năng suất mà họ có được với Rails – và hiệu quả của Go cùng Google Cloud Run, được tự động hóa thông qua Encore Cloud, đã giúp họ tiết kiệm đáng kể chi phí trong quá trình này.
“Chúng tôi đang trên đà tiết kiệm hơn 60.000 USD mỗi năm so với ứng dụng Rails cũ của chúng tôi.”
— Mason Stewart, CTO tại Bookshop.org
Quiqup
Quiqup gặp phải giới hạn hiệu suất với Rails và muốn chuyển sang Go – nhưng không muốn gặp rắc rối với kiến trúc microservices. Mô hình modular monolith của Encore đã tạo ra sự cân bằng hoàn hảo.
“Tôi thích khái niệm modular monolith của Encore. Microservices quá nặng nề để quản lý mà không có một đội ngũ lớn, và monoliths có những vấn đề riêng. Encore là sự cân bằng hoàn hảo.”
— Danny Hawkins, CTO tại Quiqup
Ngày nay, Quiqup vận hành hơn 30 dịch vụ và hơn 200 điểm cuối API – mà không cần kỹ sư DevOps chuyên trách – nhờ vào hạ tầng tự động và khả năng quan sát của Encore Cloud.
“Nếu chúng tôi đào tạo thành viên mới về Go mà không có Encore, sẽ mất gấp 2-3 lần thời gian để họ làm việc hiệu quả.”
— Danny Hawkins, CTO tại Quiqup
Kết Luận: Go Mạnh Mẽ, Nhưng Cần Cấu Trúc Cho Đội Nhóm
Tính tối giản của Go là một tính năng tuyệt vời – nhưng không phải khi nó làm chậm các đội nhóm.
Encore.go mang lại cấu trúc, cài đặt mặc định và công cụ mà Go thiếu, giúp các đội nhóm triển khai nhanh hơn với ít vấn đề về DevOps và ít công sức lặp lại hơn.
Nó không phải là về việc thay thế những điểm mạnh của Go, mà là về việc không buộc mọi đội nhóm phải tự tái tạo lại cùng một nền tảng mỗi lần bắt đầu.