feat: release v0.28 generic Go project scaffolding and lifecycle entry
This commit is contained in:
6
examples/program/.gitignore
vendored
Normal file
6
examples/program/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
/program
|
||||
/var/
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
/.goOmni/project.lock
|
||||
11
examples/program/.goOmni/projectManifest.json
Normal file
11
examples/program/.goOmni/projectManifest.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"version": 1,
|
||||
"kind": "project",
|
||||
"templateVersion": "v0.28.0-preview",
|
||||
"module": "example.com/program",
|
||||
"generated": {
|
||||
"internal/config/loadGen.go": "8a1a2746f46596a7893832ca411bd7e2d23978661943d322ee732d2c095cbe47",
|
||||
"internal/svc/runtimeGen.go": "0eb681169112302add633cc7355fbd19844b01fa0b38b94625ec18127e5fb937",
|
||||
"main.go": "36168d3e3fe822f74cede8eac3fffe5502174899e242c41526f6ad5cab7af4fd"
|
||||
}
|
||||
}
|
||||
18
examples/program/README.md
Normal file
18
examples/program/README.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# 通用程序示例
|
||||
|
||||
由 `go-omni project new` 生成,入口、加载和装配文件未经修改。项目仅在配置添加 `once`,在 `internal/run/run.go` 决定完成即退出或等待信号;无端口、数据库、队列和业务任务。
|
||||
|
||||
```sh
|
||||
cd examples/program
|
||||
go mod tidy
|
||||
go run . -check-config
|
||||
go run . # once: true,完成即退出
|
||||
# 编辑 etc/config.yaml 为 once: false 后运行,用 Ctrl+C / SIGTERM 停止。
|
||||
go build -o program .
|
||||
go-omni project check
|
||||
go test ./...
|
||||
```
|
||||
|
||||
使用同版远程 v0.28.0-preview CLI 与框架,无本地 replace。源码发布前开发可临时 `go mod edit -replace git.yuntuo.hk/k2safe/go-omni=../..`,验证后移除此本地替换。
|
||||
|
||||
配置、svc、Run 归项目维护;main/Gen.go 和清单由生成器维护。背景组件在 Init 登记停止与排空,已接受工作使用 WorkContext;未结束时不会提前释放共享依赖。详细规则见 [PROGRAM.md](../../PROGRAM.md)。
|
||||
12
examples/program/etc/config.yaml
Normal file
12
examples/program/etc/config.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
# Project-owned. Environment references are explicit: ${NAME} or ${NAME:-fallback}.
|
||||
log:
|
||||
level: info
|
||||
format: json
|
||||
output: stderr
|
||||
lifecycle:
|
||||
stopTimeout: 5s
|
||||
drainTimeout: 10s
|
||||
cancelTimeout: 5s
|
||||
|
||||
# true: finite run; false: wait for SIGINT/SIGTERM.
|
||||
once: true
|
||||
5
examples/program/go.mod
Normal file
5
examples/program/go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module example.com/program
|
||||
|
||||
go 1.22.0
|
||||
|
||||
require git.yuntuo.hk/k2safe/go-omni v0.28.0-preview
|
||||
29
examples/program/internal/config/config.go
Normal file
29
examples/program/internal/config/config.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
omniConfig "git.yuntuo.hk/k2safe/go-omni/config"
|
||||
"git.yuntuo.hk/k2safe/go-omni/lifecycle"
|
||||
"git.yuntuo.hk/k2safe/go-omni/logx"
|
||||
)
|
||||
|
||||
// Config, Default and Validate are project-owned. Add project settings here.
|
||||
type Config struct {
|
||||
Once bool `json:"once"`
|
||||
Log logx.Config `json:"log"`
|
||||
Lifecycle lifecycle.ShutdownConfig `json:"lifecycle"`
|
||||
}
|
||||
|
||||
func Default() Config {
|
||||
log := logx.Default()
|
||||
log.Service = "example.com/program"
|
||||
return Config{Once: true, Log: log, Lifecycle: lifecycle.DefaultShutdownConfig()}
|
||||
}
|
||||
func (c Config) Validate() error {
|
||||
if err := c.Log.Validate(); err != nil {
|
||||
return omniConfig.AtField("log", err)
|
||||
}
|
||||
if err := c.Lifecycle.Validate(); err != nil {
|
||||
return omniConfig.AtField("lifecycle", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
12
examples/program/internal/config/loadGen.go
Normal file
12
examples/program/internal/config/loadGen.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Code generated by go-omni project. DO NOT EDIT.
|
||||
package config
|
||||
|
||||
import omniConfig "git.yuntuo.hk/k2safe/go-omni/config"
|
||||
|
||||
func Load(path string) (Config, error) {
|
||||
loader, err := NewLoader()
|
||||
if err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
return omniConfig.LoadProject(path, Default(), loader)
|
||||
}
|
||||
6
examples/program/internal/config/loader.go
Normal file
6
examples/program/internal/config/loader.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package config
|
||||
|
||||
import omniConfig "git.yuntuo.hk/k2safe/go-omni/config"
|
||||
|
||||
// NewLoader is project-owned. Register additional decoders here if needed.
|
||||
func NewLoader() (*omniConfig.Loader, error) { return omniConfig.NewLoader() }
|
||||
19
examples/program/internal/run/run.go
Normal file
19
examples/program/internal/run/run.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package run
|
||||
|
||||
import (
|
||||
"context"
|
||||
"example.com/program/internal/svc"
|
||||
)
|
||||
|
||||
// Run is project-owned. Return nil for normal completion, or an error to fail.
|
||||
// The starter waits for shutdown; a one-shot program can simply return when done.
|
||||
// Register background stop/drain hooks in svc.Init, not concurrently here.
|
||||
// Accepted background work uses deps.WorkContext(), not the early-stop ctx.
|
||||
func Run(ctx context.Context, deps *svc.ServiceContext) error {
|
||||
if deps.Config.Once {
|
||||
deps.Logger.InfoContext(ctx, "program.completed")
|
||||
return nil
|
||||
}
|
||||
<-ctx.Done()
|
||||
return nil
|
||||
}
|
||||
44
examples/program/internal/svc/runtimeGen.go
Normal file
44
examples/program/internal/svc/runtimeGen.go
Normal file
@@ -0,0 +1,44 @@
|
||||
// Code generated by go-omni project. DO NOT EDIT.
|
||||
package svc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"example.com/program/internal/config"
|
||||
"fmt"
|
||||
"git.yuntuo.hk/k2safe/go-omni/lifecycle"
|
||||
"git.yuntuo.hk/k2safe/go-omni/logx"
|
||||
)
|
||||
|
||||
func New(ctx context.Context, logger *logx.Logger, cfg config.Config) (result *ServiceContext, err error) {
|
||||
if ctx == nil {
|
||||
ctx = context.Background()
|
||||
}
|
||||
if err = ctx.Err(); err != nil {
|
||||
return nil, errors.Join(err, lifecycle.Cause(ctx))
|
||||
}
|
||||
if logger == nil {
|
||||
return nil, errors.New("service context requires a logger")
|
||||
}
|
||||
if err = cfg.Validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
runtime, err := lifecycle.NewRuntimeWithConfig(logx.WithLogger(ctx, logger), cfg.Lifecycle)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c := &ServiceContext{Runtime: runtime, Config: cfg, Logger: logger}
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
err = fmt.Errorf("dependency initialization panic: %v", p)
|
||||
}
|
||||
if err != nil {
|
||||
result = nil
|
||||
err = c.Finish(err)
|
||||
}
|
||||
}()
|
||||
if err = c.Init(c.Context()); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
24
examples/program/internal/svc/serviceContext.go
Normal file
24
examples/program/internal/svc/serviceContext.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package svc
|
||||
|
||||
import (
|
||||
"context"
|
||||
"example.com/program/internal/config"
|
||||
"git.yuntuo.hk/k2safe/go-omni/lifecycle"
|
||||
"git.yuntuo.hk/k2safe/go-omni/logx"
|
||||
)
|
||||
|
||||
// ServiceContext is project-owned. Add dependencies without global variables.
|
||||
// Runtime owns registered resources; Logger is borrowed from the entry point.
|
||||
type ServiceContext struct {
|
||||
*lifecycle.Runtime
|
||||
Config config.Config
|
||||
Logger *logx.Logger
|
||||
}
|
||||
|
||||
// Init runs before Run. Register resources immediately with OnClose.
|
||||
// Register paired OnStop/OnDrain hooks before exposing background work.
|
||||
// Use WorkContext for accepted work; Context is the early stop notification.
|
||||
// OnDrain returns nil only after processing and final cleanup have completed.
|
||||
// Complete all registrations here, before Run can race with shutdown.
|
||||
// The drain name "go-omni/project/run" is reserved for the generated entry.
|
||||
func (c *ServiceContext) Init(ctx context.Context) error { return nil }
|
||||
89
examples/program/main.go
Normal file
89
examples/program/main.go
Normal file
@@ -0,0 +1,89 @@
|
||||
// Code generated by go-omni project. DO NOT EDIT.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"example.com/program/internal/config"
|
||||
program "example.com/program/internal/run"
|
||||
"example.com/program/internal/svc"
|
||||
"flag"
|
||||
"fmt"
|
||||
"git.yuntuo.hk/k2safe/go-omni/lifecycle"
|
||||
"git.yuntuo.hk/k2safe/go-omni/logx"
|
||||
"log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func main() {
|
||||
path := flag.String("config", "etc/config.yaml", "configuration file; empty uses Default()")
|
||||
check := flag.Bool("check-config", false, "validate configuration without initializing dependencies")
|
||||
flag.Parse()
|
||||
if *check {
|
||||
if _, err := config.Load(*path); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
return
|
||||
}
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
|
||||
defer stop()
|
||||
if err := run(ctx, *path); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func run(ctx context.Context, path string) (runErr error) {
|
||||
cfg, err := config.Load(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
logger, err := logx.New(cfg.Log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var deps *svc.ServiceContext
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
runErr = fmt.Errorf("program initialization panic: %v", p)
|
||||
}
|
||||
if deps != nil {
|
||||
runErr = deps.Finish(runErr)
|
||||
}
|
||||
// Unjoined work may still use its dependencies and logger. The process exits
|
||||
// unsuccessfully; resource retention never means the work completed.
|
||||
if !errors.Is(runErr, lifecycle.ErrResourcesRetained) {
|
||||
runErr = errors.Join(runErr, logger.Close())
|
||||
}
|
||||
}()
|
||||
deps, err = svc.New(ctx, logger, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if deps.Context().Err() != nil {
|
||||
return nil
|
||||
}
|
||||
done := make(chan struct{})
|
||||
// This reserved drain proves Run has actually returned, including its defers.
|
||||
// Runtime bounds the wait even when Run ignores cancellation.
|
||||
deps.OnDrain("go-omni/project/run", func(context.Context) error { <-done; return nil })
|
||||
logger.InfoContext(deps.Context(), "program.starting")
|
||||
go func() {
|
||||
var result error
|
||||
returned := false
|
||||
defer func() {
|
||||
if p := recover(); p != nil {
|
||||
result = fmt.Errorf("program run panic: %v", p)
|
||||
} else if !returned {
|
||||
result = errors.New("program run exited without returning")
|
||||
}
|
||||
deps.RequestShutdown(result)
|
||||
close(done)
|
||||
}()
|
||||
result = program.Run(deps.Context(), deps)
|
||||
returned = true
|
||||
}()
|
||||
<-deps.Context().Done()
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user