Khái niệm cốt lõi
Hướng dẫn này giải thích các thành phần nền tảng của Fountain: singleton của ứng dụng, vòng đời của các runnable cùng thứ tự chạy, các kiểu runnable mà bạn đăng ký, các core controller cùng observer pattern của chúng, hai mô hình tích hợp thư viện, và các quy ước mà framework tuân theo. Hiểu được những khái niệm này là nền tảng cho mọi thứ khác trong framework.
Singleton Fountain
Fountain được khởi động thông qua một struct Fountain dạng singleton. Bạn lấy nó bằng New(), đăng ký các thành phần muốn chạy bằng các builder method theo phong cách fluent, rồi khởi chạy vòng đời bằng Serving(). New() luôn trả về cùng một instance ở mọi lần gọi (nó được khởi tạo một lần duy nhất qua sync.Once), nên mọi cấu hình bạn tích lũy qua các builder method đều áp dụng cho một ứng dụng dùng chung.
Các builder method đăng ký bốn kiểu runnable và trả về *Fountain để có thể nối chuỗi lời gọi:
WithAppInstances(appInstances ...runnable.AppInstance)— các server chạy lâu dài.WithInvokers(invokers ...runnable.Invoker)— các hàm khởi tạo chạy một lần.WithJobs(jobs ...runnable.JobInstance)— các job chạy một lần.WithCrons(crons ...runnable.JobInstance)— các job chạy theo lịch.
Serving() là điểm vào dạng blocking, chạy các thành phần đã đăng ký theo thứ tự và giữ tiến trình hoạt động cho đến khi nhận tín hiệu tắt.
fountain.New().
WithAppInstances(httpServer, grpcServer).
WithInvokers(initDB).
WithCrons(dailyReport).
Serving()Vòng đời & thứ tự chạy
Khi bạn gọi Serving(), Fountain chạy các kiểu đã đăng ký theo một thứ tự cố định:
Invokers → Jobs → Crons → AppInstances → Auxiliary
- Invokers chạy đầu tiên. Chúng thực thi song song khi cờ
concurrencybật, ngược lại thì chạy tuần tự. - Jobs chạy kế tiếp, tuân theo cùng quy tắc concurrency như invokers.
- Crons chạy sau đó. Khi có crons, cả hai cờ
concurrencyvàhangsẽ tự động được bật để chúng chạy song song và giữ ứng dụng hoạt động. - AppInstances sau đó được khởi tạo và phục vụ.
concurrencyvàhangcũng được bật ở đây để các server có thể chạy song song và giữ tiến trình hoạt động liên tục. - Auxiliary server (metrics / governance) khởi chạy khi
hangđược bật, giữ ứng dụng hoạt động cho đến khi nhận được tín hiệu tắt (shutdownSignals, ví dụSIGTERM/SIGINT).
Hai cờ chi phối hành vi này:
concurrency— ảnh hưởng tới invokers và jobs. Khi bật, các thành phần này được khởi chạy trong các goroutine (phối hợp bằngsync.WaitGroup) và chạy song song. Khi tắt, chúng chạy tuần tự: mỗi thành phần phải hoàn tất trước khi thành phần kế tiếp bắt đầu.hang— giữ ứng dụng ở trạng thái chờ thay vì thoát ngay sau khi công việc hoàn tất. Nó thường đi kèm vớiconcurrencyđể tiến trình chờ tín hiệu tắt từ hệ thống. Khihangtắt,Serving()sẽ không chờ và thoát ngay khi công việc đã đăng ký hoàn thành — phù hợp cho các tác vụ ngắn và nhanh gọn.
Khi tắt, Fountain chạy các hàm beforeStopClean, dừng jobs và crons, hủy các instance, rồi chạy các hàm afterStopClean.
Các kiểu runnable
Các thành phần bạn đăng ký hiện thực các interface được định nghĩa trong package runnable.
AppInstance
AppInstance là một server chạy lâu dài (server HTTP, gRPC, TCP, UDP, hoặc QUIC). Fountain khởi tạo nó, chạy Serving() trong một goroutine, và hủy nó khi tắt. Interface như sau:
// AppInstance type;
type AppInstance interface {
Initialize() error
// Serving was called in a goroutine
Serving()
Destroy() error
OnConfigChange()
Info() ServerInstance
}Initialize() chuẩn bị instance, Serving() chạy server (được gọi trong một goroutine, nên có thể blocking), Destroy() giải phóng tài nguyên khi tắt, OnConfigChange() là callback được kích hoạt khi cấu hình thay đổi, và Info() trả về metadata ServerInstance (tên, ID, protocol, address, v.v.) được dùng cho những việc như service discovery.
Invoker
Invoker là một hàm khởi tạo chạy một lần — kiểu runnable đơn giản nhất. Nó chỉ là một hàm trả về error:
type Invoker func() errorInvokers chạy đầu tiên trong vòng đời, khiến chúng là nơi phù hợp cho công việc thiết lập như kết nối tới database hoặc làm nóng (warm) cache.
JobInstance
JobInstance là một đơn vị công việc chạy một lần hoặc theo lịch. Cả WithJobs(...) (job chạy một lần) và WithCrons(...) (job theo lịch) đều nhận các giá trị JobInstance. Interface như sau:
// JobInstance type;
type JobInstance interface {
Start() error
GetName() string
Stop() error
}Start() chạy job, GetName() định danh nó, và Stop() được gọi trong quá trình tắt.
Core controller & observer pattern
Logic nghiệp vụ trong Fountain được tổ chức thành các core controller. Một controller hiện thực interface CoreController từ package core:
// CoreController interface
type CoreController interface {
InstallController()
RegisterCallback(cb any)
}Bạn đăng ký các controller bằng core.RegisterCoreController(&MyController{}), rồi kết nối chúng bằng core.InstallCoreControllers() một khi các phụ thuộc của chúng (database, cache, v.v.) đã sẵn sàng. Trong quá trình cài đặt, Fountain gọi InstallController() trên từng controller, rồi gọi RegisterCallback(...) trên mỗi controller cho từng controller khác, cho phép các controller khám phá lẫn nhau.
core.RegisterCoreController(&UserController{})
core.RegisterCoreController(&OrderController{})
// after databases, caches, etc. are installed:
core.InstallCoreControllers()Các controller giao tiếp thông qua một observer pattern. Một controller muốn nhận sự kiện sẽ hiện thực interface generic CoreControllerObserver[T] (một method Update(event EventType, params ...T)). Để phát một sự kiện tới tất cả các controller quan sát kiểu đó, hãy gọi core.NotifyObservers(...):
core.NotifyObservers[*User](EventUserCreated, user)NotifyObservers duyệt qua các controller đã đăng ký và gọi Update(...) trên bất kỳ controller nào hiện thực interface observer khớp, giữ cho các controller tách rời (decoupled) mà vẫn có thể phản ứng với sự kiện của nhau.
Mô hình tích hợp
Các thư viện hạ tầng (server, database, cache, broker, client) được cài đặt vào instance Fountain thông qua một trong hai mô hình.
Mô hình config-key — gắn thành phần với một key lồng nhau trong config.yaml rồi cài đặt nó:
client := fedis.WithConfigKey("redis").InstallFountainInstance()Mô hình functional-options — cấu hình thành phần ngay tại chỗ bằng các hàm option With*:
server := fgrpc.WithConfigKey("fgrpc").
InstallFountainInstance(fgrpc.WithServerOptions(serverOptions...))Hai mô hình có thể kết hợp: một dạng phổ biến là chọn config key bằng WithConfigKey(...) rồi truyền các functional option vào InstallFountainInstance(...). Cấu hình nằm trong config.yaml với các key lồng nhau, và biến môi trường dùng tiền tố FOUNTAIN_.
Quy ước
Fountain tuân theo một tập quy ước nhất quán trong toàn bộ codebase:
- Đặt tên package — các package tiện ích/thư viện dùng tiền tố
f*:flog,fedis,fhttp,fgrpc, v.v. Các thư mục protocol client dùngf<protocol>_client/. - Hằng số — các hằng số toàn cục dùng tiền tố
Kở dạngUPPER_SNAKE_CASE(ví dụKPackageName,KServerKindProvider). - Thứ tự import — nhóm các import theo: thư viện chuẩn → bên thứ ba →
gitlab.soludian.com/soludian/fountain/...→ các package local. - Logging — dùng
flog, không dùng packagelogcủa thư viện chuẩn. Lấy logger bằngflog.NewFountainLoggerOnce(). - Lỗi — dùng các helper trong
libs/ferr/để bao gói (wrap) và xử lý lỗi.