在實現了 service.(AOF) 之後,我們需要思考如何優雅地使用這個介面。
誠然,直接在 SET 指令裡注入這個服務,並且當寫入 repo.(*mapStorage) 時直接把指令寫入 AOF 這確實是個辦法,只是這很不優雅--如此一來,違背了 ./internal/serivce/cmd/ 的設計初衷,正因為想要專注在指令功能上的開發才使用工廠模式,這麼做就像走了回頭路。
在「請求 - 回應」的模型中,常常會使用到 Middleware(中介層) 設計模式,最典型的就是資源管理類的服務:
請求 -> 身份驗證中介層(Authenticator) -> 資源操作 -> 審計記錄中介層(Audit) -> 回應
在資源操作前,通常會先驗證身份,並且確認該身份有操作的權限(Authentication 與 Authorization),然後在資源操作之後,還需要記錄這次操作的情況,未來若有什麼問題可以追責或恢復。
在資料持久化的場景中,我們可以想像以下流程:
RESP 請求 -> Storage 操作 -> AOF Writer 中介層 -> 回應
在 Go 中設計 Middleware 模式,通常會用 func(HandlerFunc) HandlerFunc:
註:這邊本來有一個 Code Block,但是因為 ithome 的 cloudflare 規則擋掉了所以存不進來,這邊用圖片代替。

server.NewAOFMiddleware()// file: ./internal/server/middleware.go
package server
import (
"context"
"fmt"
"olivine/internal/service"
"olivine/pkg/resp"
)
func NewAOFMiddleware(aof service.AOF) Middleware {
return func(next HandlerFunc) HandlerFunc {
return func(ctx context.Context, cmd *resp.Command) (resp.Value, error) {
// 這邊會讓 server.(Handler).ServeRESP 先執行完請求
ret, err := next(ctx, cmd)
if err != nil {
return ret, err
}
// 沒有錯誤的話才會執行 aof.Write()
if err := aof.Write(cmd); err != nil {
return nil, fmt.Errorf("%w: failed to write AOF: %w", ErrServer, err)
}
return ret, nil
}
}
}
main.(*App) 中註冊 Middleware// file: ./cmd/olivine/app.go
func NewApp() (*App, error) {
aof, err := service.NewAOF("database.aof")
if err != nil {
return nil, err
}
return &App{
srv: server.NewServer(server.NewHandler(cmd.NewCommands(repo.NewStorage()), server.NewAOFMiddleware(aof))),
}, nil
}