Ver código fonte

出版完成

xbase 3 semanas atrás
pai
commit
6f73c536a1
8 arquivos alterados com 655 adições e 2 exclusões
  1. 150 2
      README.md
  2. 154 0
      cmd/xpc/main.go
  3. 35 0
      example/main.go
  4. 10 0
      example/xpc_gen.go
  5. 3 0
      go.mod
  6. 54 0
      registry.go
  7. 150 0
      runtime.go
  8. 99 0
      xpc.go

+ 150 - 2
README.md

@@ -1,3 +1,151 @@
-# xpc
+# XPC: Next-Gen Go Microservice Framework
 
-微服务完整框架
+**XPC** 是一个受 Google Service Weaver 启发,专为 Go 语言打造的现代、高性能、零依赖的微服务开发框架。
+
+它的核心理念是:**"像开发单体应用一样开发微服务,运行时既可以单体发布运行,也可以像管理集群一样管理运行时部分或者全部。"**
+
+我们推崇 **约束大于配置 (Convention over Configuration)**,通过代码层面的最佳实践约束,消除繁琐的配置文件,让开发者专注于业务逻辑。
+
+## 核心特性 (Features)
+
+*   **逻辑单体,物理分布 (Logical Monolith, Physical Distribution)**
+    *   开发者编写代码如同在写一个模块化的单体应用。
+    *   **灵活部署**:编译出的二进制文件既可以直接作为单体应用在单机运行(生产级性能,本地函数调用),也可以通过运行时配置,将特定组件动态拆分到不同节点上运行。
+    *   **生命周期管理**:内置 `Init(ctx)` 和 `Shutdown(ctx)` 生命周期钩子,确保资源正确初始化和释放。
+
+*   **零外部依赖 (Zero Dependencies)**
+    *   完全基于 Go 标准库构建。
+    *   拒绝臃肿,追求极致的轻量化和编译速度。
+
+*   **Raft 驱动的智能运行时 (Raft-Powered Runtime)**
+    *   (Planned) 集成高性能 Raft 共识算法。
+    *   **去中心化元数据管理**:无需依赖外部 Etcd 或 Consul。
+    *   **安全优先**:内置 TLS 支持,确保集群通信安全。
+
+*   **极简代码风格 (Minimalist API)**
+    *   **直觉化 API**: `xpc.Run` 自动处理依赖注入和优雅退出 (Graceful Shutdown)。
+    *   **自动化**: `xpc gen` 自动生成注册代码,减少样板。
+    *   **扁平化结构**: 源码结构扁平,核心逻辑文件平铺在项目根目录,拒绝深度嵌套。
+
+## 架构设计 (Architecture)
+
+XPC 框架经过深度优化,分为三层:
+
+### 1. API 层 (Public API)
+提供极其精简的接口:
+- `xpc.Implements[T]`: 标记组件实现。
+- `xpc.Run(ctx, app, opts...)`: 启动应用,自动处理信号、生命周期和依赖注入。
+
+```go
+type Calculator interface {
+    Add(ctx context.Context, a, b int) (int, error)
+}
+```
+
+### 2. 核心运行时 (Core Runtime)
+位于根目录的 `runtime.go` 和 `registry.go`,高度模块化但结构扁平:
+- **Local Runtime**: 针对单机模式极致优化,组件间通信为直接内存函数调用,**零网络开销**。
+- **Cluster Runtime**: (开发中) 基于 Raft 的分布式运行时,支持 mTLS 安全通信。
+- **Registry**: 线程安全的组件注册中心,支持热加载准备。
+
+### 3. 工具链 (Toolchain)
+`cmd/xpc` 提供代码生成工具,扫描源码并自动注册组件。
+
+## 项目结构规范 (Project Structure)
+
+我们遵循极简的**扁平化目录结构**:
+
+*   **核心库文件**:所有核心 `.go` 文件平铺在项目根目录(如 `xpc.go`, `runtime.go`, `registry.go`)。
+*   **`cmd/`**: 存放 CLI 工具入口。
+*   **`example/`**: 存放示例代码。
+*   **无 `internal/`**: 除非绝对必要,否则不使用深层嵌套的 `internal` 目录,通过大小写控制可见性。
+
+## 快速开始 (Getting Started)
+
+### 1. 定义组件
+
+```go
+package main
+
+import (
+    "context"
+    "fmt"
+    "igit.com/robert/xpc"
+)
+
+// 定义接口
+type HelloService interface {
+    SayHello(ctx context.Context, name string) (string, error)
+}
+
+// 实现接口
+type helloServiceImpl struct {
+    xpc.Implements[HelloService]
+}
+
+// 可选:初始化钩子
+func (h *helloServiceImpl) Init(ctx context.Context) error {
+    fmt.Println("Service Initialized")
+    return nil
+}
+
+func (h *helloServiceImpl) SayHello(ctx context.Context, name string) (string, error) {
+    return "Hello, " + name, nil
+}
+```
+
+### 2. 运行应用
+
+```go
+func main() {
+    // 启动 XPC,自动处理依赖注入和优雅退出
+    err := xpc.Run(context.Background(), func(ctx context.Context, hello HelloService) error {
+        res, _ := hello.SayHello(ctx, "World")
+        println(res)
+        return nil // 返回后 XPC 会自动执行 Shutdown
+    })
+    
+    if err != nil {
+        panic(err)
+    }
+}
+```
+
+### 3. 生成代码与运行
+
+```bash
+# 生成注册代码
+go run cmd/xpc/main.go gen
+
+# 运行
+go run .
+```
+
+## 安全与配置 (Security & Config)
+
+XPC 优先考虑安全性。在启动时可以配置 TLS 证书,供集群模式使用:
+
+```go
+xpc.Run(ctx, app, xpc.Options{
+    TLSCertFile: "/path/to/cert.pem",
+    TLSKeyFile:  "/path/to/key.pem",
+})
+```
+
+## 路线图 (Roadmap)
+
+- [x] **Phase 1: Core Framework Refactoring**
+    - [x] 模块化运行时架构 (Internal Runtime)。
+    - [x] 生命周期管理 (`Init`/`Shutdown`)。
+    - [x] 优雅退出与信号处理。
+    - [x] 基础依赖注入系统。
+    - [x] 扁平化项目结构。
+
+- [ ] **Phase 2: Distribution & Security**
+    - [ ] 实现 Cluster Runtime (Raft integration).
+    - [ ] 实现 mTLS 通信层。
+    - [ ] 完善 `xpc gen` 支持跨包接口。
+
+## 贡献 (Contributing)
+
+本项目遵循严格的代码规范:**简洁、原生、高性能**。

+ 154 - 0
cmd/xpc/main.go

@@ -0,0 +1,154 @@
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"go/ast"
+	"go/format"
+	"go/parser"
+	"go/token"
+	"os"
+	"text/template"
+)
+
+func main() {
+	if len(os.Args) < 2 {
+		fmt.Println("Usage: xpc <command>")
+		os.Exit(1)
+	}
+
+	switch os.Args[1] {
+	case "gen":
+		if err := runGen("."); err != nil {
+			fmt.Fprintf(os.Stderr, "Error: %v\n", err)
+			os.Exit(1)
+		}
+	default:
+		fmt.Printf("Unknown command: %s\n", os.Args[1])
+	}
+}
+
+type Registration struct {
+	InterfaceName string
+	StructName    string
+	PackageName   string
+}
+
+func runGen(dir string) error {
+	fset := token.NewFileSet()
+	pkgs, err := parser.ParseDir(fset, dir, nil, parser.ParseComments)
+	if err != nil {
+		return err
+	}
+
+	for pkgName, pkg := range pkgs {
+		var registrations []Registration
+
+		for _, file := range pkg.Files {
+			ast.Inspect(file, func(n ast.Node) bool {
+				// Find struct declarations
+				typeSpec, ok := n.(*ast.TypeSpec)
+				if !ok {
+					return true
+				}
+				structType, ok := typeSpec.Type.(*ast.StructType)
+				if !ok {
+					return true
+				}
+
+				// Check for xpc.Implements[T] field
+				for _, field := range structType.Fields.List {
+					// xpc.Implements is usually embedded, so no names
+					if len(field.Names) > 0 {
+						continue
+					}
+
+					// Check type expression
+					indexExpr, ok := field.Type.(*ast.IndexExpr)
+					if !ok {
+						continue
+					}
+					
+					// Check if it is xpc.Implements (simplified check)
+					// In a robust tool we'd check imports, but here we assume "xpc" or "xpc.Implements"
+					selExpr, ok := indexExpr.X.(*ast.SelectorExpr)
+					if !ok {
+						// Could be just Implements if dot-imported, but let's assume xpc.Implements
+						continue
+					}
+					
+					if selExpr.Sel.Name == "Implements" {
+						// Extract Interface T
+						// T can be an Ident or SelectorExpr
+						var interfaceName string
+						switch t := indexExpr.Index.(type) {
+						case *ast.Ident:
+							interfaceName = t.Name
+						case *ast.SelectorExpr: // otherpkg.Interface
+							if x, ok := t.X.(*ast.Ident); ok {
+								interfaceName = x.Name + "." + t.Sel.Name
+							}
+						}
+
+						if interfaceName != "" {
+							registrations = append(registrations, Registration{
+								InterfaceName: interfaceName,
+								StructName:    typeSpec.Name.Name,
+								PackageName:   pkgName,
+							})
+						}
+					}
+				}
+				return true
+			})
+		}
+
+		if len(registrations) > 0 {
+			if err := generateFile(dir, pkgName, registrations); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+const genTemplate = `// Code generated by xpc. DO NOT EDIT.
+package {{.PackageName}}
+
+import (
+	"igit.com/robert/xpc"
+)
+
+func init() {
+{{- range .Registrations }}
+	xpc.Register[{{.InterfaceName}}, {{.StructName}}](func() any { return &{{.StructName}}{} })
+{{- end }}
+}
+`
+
+func generateFile(dir, pkgName string, regs []Registration) error {
+	t := template.Must(template.New("gen").Parse(genTemplate))
+	var buf bytes.Buffer
+	data := struct {
+		PackageName   string
+		Registrations []Registration
+	}{
+		PackageName:   pkgName,
+		Registrations: regs,
+	}
+
+	if err := t.Execute(&buf, data); err != nil {
+		return err
+	}
+
+	// Format code
+	formatted, err := format.Source(buf.Bytes())
+	if err != nil {
+		// If formatting fails, write unformatted for debugging
+		formatted = buf.Bytes()
+	}
+
+	filename := fmt.Sprintf("%s/xpc_gen.go", dir)
+	return os.WriteFile(filename, formatted, 0644)
+}
+

+ 35 - 0
example/main.go

@@ -0,0 +1,35 @@
+package main
+
+import (
+	"context"
+	"fmt"
+	"igit.com/robert/xpc"
+)
+
+// 定义接口
+type HelloService interface {
+	SayHello(ctx context.Context, name string) (string, error)
+}
+
+// 实现接口
+type helloServiceImpl struct {
+	xpc.Implements[HelloService]
+}
+
+func (h *helloServiceImpl) SayHello(ctx context.Context, name string) (string, error) {
+	return "Hello, " + name, nil
+}
+
+func main() {
+	if err := xpc.Run(context.Background(), func(ctx context.Context, hello HelloService) error {
+		res, err := hello.SayHello(ctx, "World")
+		if err != nil {
+			return err
+		}
+		fmt.Println(res)
+		return nil
+	}); err != nil {
+		panic(err)
+	}
+}
+

+ 10 - 0
example/xpc_gen.go

@@ -0,0 +1,10 @@
+// Code generated by xpc. DO NOT EDIT.
+package main
+
+import (
+	"igit.com/robert/xpc"
+)
+
+func init() {
+	xpc.Register[HelloService, helloServiceImpl](func() any { return &helloServiceImpl{} })
+}

+ 3 - 0
go.mod

@@ -0,0 +1,3 @@
+module igit.com/robert/xpc
+
+go 1.25.5

+ 54 - 0
registry.go

@@ -0,0 +1,54 @@
+package xpc
+
+import (
+	"reflect"
+	"sync"
+)
+
+// componentInfo holds metadata about a registered component.
+type componentInfo struct {
+	Name          string
+	InterfaceType reflect.Type
+	ImplType      reflect.Type
+	NewFunc       func() any // Returns a pointer to the implementation
+}
+
+var (
+	registryMu sync.RWMutex
+	components = make(map[reflect.Type]*componentInfo)
+	byName     = make(map[string]*componentInfo)
+)
+
+// registerComponent registers a component implementation.
+// intfPtr: (*Interface)(nil)
+// implPtr: (*Implementation)(nil) or the implementation type instance
+// newFunc: function that returns a new instance of the implementation
+func registerComponent(intfPtr any, implPtr any, newFunc func() any) {
+	registryMu.Lock()
+	defer registryMu.Unlock()
+
+	intfType := reflect.TypeOf(intfPtr).Elem()
+	implType := reflect.TypeOf(implPtr)
+	if implType.Kind() == reflect.Ptr {
+		implType = implType.Elem()
+	}
+
+	info := &componentInfo{
+		Name:          intfType.PkgPath() + "." + intfType.Name(),
+		InterfaceType: intfType,
+		ImplType:      implType,
+		NewFunc:       newFunc,
+	}
+
+	components[intfType] = info
+	byName[info.Name] = info
+}
+
+// getComponentInfo returns the component info for the given interface type.
+func getComponentInfo(intfType reflect.Type) (*componentInfo, bool) {
+	registryMu.RLock()
+	defer registryMu.RUnlock()
+	info, ok := components[intfType]
+	return info, ok
+}
+

+ 150 - 0
runtime.go

@@ -0,0 +1,150 @@
+package xpc
+
+import (
+	"context"
+	"fmt"
+	"reflect"
+	"sync"
+)
+
+// runtimeConfig holds runtime configuration.
+type runtimeConfig struct {
+	TLSCertFile string
+	TLSKeyFile  string
+}
+
+// runtime defines the behavior of the XPC runtime.
+type runtime interface {
+	// getInstance returns a client or instance for the given component interface.
+	getInstance(ctx context.Context, intfType reflect.Type) (any, error)
+	// shutdown gracefully stops the runtime and its components.
+	shutdown(ctx context.Context) error
+}
+
+// newLocalRuntime creates a new local runtime.
+func newLocalRuntime(cfg runtimeConfig) runtime {
+	return &localRuntime{
+		instances: make(map[reflect.Type]any),
+		config:    cfg,
+	}
+}
+
+// newClusterRuntime creates a new cluster runtime.
+func newClusterRuntime(cfg runtimeConfig) runtime {
+	return &clusterRuntime{
+		config: cfg,
+	}
+}
+
+// localRuntime executes components in the same process.
+type localRuntime struct {
+	mu        sync.Mutex
+	instances map[reflect.Type]any
+	config    runtimeConfig
+}
+
+func (r *localRuntime) getInstance(ctx context.Context, intfType reflect.Type) (any, error) {
+	r.mu.Lock()
+	if instance, ok := r.instances[intfType]; ok {
+		r.mu.Unlock()
+		return instance, nil
+	}
+	r.mu.Unlock() // Unlock before potentially long init
+
+	info, ok := getComponentInfo(intfType)
+	if !ok {
+		return nil, fmt.Errorf("component implementation not found for interface %s", intfType.Name())
+	}
+
+	// Double-check locking for creation
+	r.mu.Lock()
+	if instance, ok := r.instances[intfType]; ok {
+		r.mu.Unlock()
+		return instance, nil
+	}
+
+	// Create new instance
+	implPtr := info.NewFunc()
+
+	// Store in cache before injection to support circular dependencies
+	r.instances[intfType] = implPtr
+	r.mu.Unlock()
+
+	// Perform dependency injection
+	if err := r.inject(ctx, implPtr); err != nil {
+		return nil, err
+	}
+
+	// Lifecycle: Init
+	if initializer, ok := implPtr.(interface{ Init(context.Context) error }); ok {
+		if err := initializer.Init(ctx); err != nil {
+			return nil, fmt.Errorf("component %s Init failed: %w", info.Name, err)
+		}
+	}
+
+	return implPtr, nil
+}
+
+func (r *localRuntime) inject(ctx context.Context, implPtr any) error {
+	val := reflect.ValueOf(implPtr).Elem()
+	typ := val.Type()
+
+	for i := 0; i < val.NumField(); i++ {
+		field := val.Field(i)
+		fieldType := typ.Field(i)
+
+		// Skip unexported fields
+		if !fieldType.IsExported() {
+			continue
+		}
+
+		// If field is an interface and registered as a component, inject it
+		if field.Kind() == reflect.Interface {
+			if _, ok := getComponentInfo(field.Type()); ok {
+				dep, err := r.getInstance(ctx, field.Type())
+				if err != nil {
+					return fmt.Errorf("failed to inject dependency %s: %w", fieldType.Name, err)
+				}
+				field.Set(reflect.ValueOf(dep))
+			}
+		}
+	}
+	return nil
+}
+
+func (r *localRuntime) shutdown(ctx context.Context) error {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+
+	var errs []error
+	// In local mode, we just shutdown all instantiated components.
+	// Note: Order isn't guaranteed here. For strict ordering, we'd need a dependency graph.
+	for _, instance := range r.instances {
+		if closer, ok := instance.(interface{ Shutdown(context.Context) error }); ok {
+			if err := closer.Shutdown(ctx); err != nil {
+				errs = append(errs, err)
+			}
+		}
+	}
+
+	if len(errs) > 0 {
+		return fmt.Errorf("shutdown errors: %v", errs)
+	}
+	return nil
+}
+
+// clusterRuntime executes components across a Raft cluster.
+type clusterRuntime struct {
+	config runtimeConfig
+	// raftNode *raft.Node
+}
+
+func (r *clusterRuntime) getInstance(ctx context.Context, intfType reflect.Type) (any, error) {
+	// TODO: Phase 2 - Raft Integration
+	return nil, fmt.Errorf("cluster runtime: not implemented in this phase. use XPC_MODE=local")
+}
+
+func (r *clusterRuntime) shutdown(ctx context.Context) error {
+	return nil
+}
+

+ 99 - 0
xpc.go

@@ -0,0 +1,99 @@
+package xpc
+
+import (
+	"context"
+	"fmt"
+	"os"
+	"os/signal"
+	"reflect"
+	"syscall"
+	"time"
+)
+
+// Implements is a struct that component implementations must embed.
+// T is the component interface.
+type Implements[T any] struct{}
+
+// Register is used by generated code to register components.
+func Register[Intf any, Impl any](newFunc func() any) {
+	registerComponent((*Intf)(nil), (*Impl)(nil), newFunc)
+}
+
+// Options configuration for the XPC runtime
+type Options struct {
+	// TLSCertFile path to the TLS certificate file (for cluster mode)
+	TLSCertFile string
+	// TLSKeyFile path to the TLS key file (for cluster mode)
+	TLSKeyFile string
+}
+
+// Run starts the XPC application.
+// app must be a function with the signature: func(context.Context, Component...) error
+// It handles OS signals (SIGINT, SIGTERM) to cancel the context passed to the app.
+func Run(ctx context.Context, app any, opts ...Options) error {
+	// Parse options
+	var opt Options
+	if len(opts) > 0 {
+		opt = opts[0]
+	}
+	
+	rtConfig := runtimeConfig{
+		TLSCertFile: opt.TLSCertFile,
+		TLSKeyFile:  opt.TLSKeyFile,
+	}
+
+	appVal := reflect.ValueOf(app)
+	appType := appVal.Type()
+
+	if appType.Kind() != reflect.Func {
+		return fmt.Errorf("app must be a function, got %v", appType)
+	}
+
+	if appType.NumIn() < 1 || appType.In(0) != reflect.TypeOf((*context.Context)(nil)).Elem() {
+		return fmt.Errorf("first argument of app must be context.Context")
+	}
+
+	if appType.NumOut() != 1 || appType.Out(0) != reflect.TypeOf((*error)(nil)).Elem() {
+		return fmt.Errorf("app must return a single error")
+	}
+
+	// Setup signal handling
+	ctx, cancel := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
+	defer cancel()
+
+	// Initialize Runtime based on configuration
+	var rt runtime
+	if os.Getenv("XPC_MODE") == "cluster" {
+		rt = newClusterRuntime(rtConfig)
+	} else {
+		rt = newLocalRuntime(rtConfig)
+	}
+
+	// Instantiate requested root components
+	args := []reflect.Value{reflect.ValueOf(ctx)}
+	for i := 1; i < appType.NumIn(); i++ {
+		argType := appType.In(i)
+		comp, err := rt.getInstance(ctx, argType)
+		if err != nil {
+			return fmt.Errorf("failed to get instance for argument %d (%v): %w", i, argType, err)
+		}
+		args = append(args, reflect.ValueOf(comp))
+	}
+
+	// Execute Application
+	results := appVal.Call(args)
+	
+	// Graceful Shutdown
+	shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
+	defer shutdownCancel()
+	
+	if err := rt.shutdown(shutdownCtx); err != nil {
+		fmt.Fprintf(os.Stderr, "xpc: shutdown error: %v\n", err)
+	}
+
+	errVal := results[0]
+	if !errVal.IsNil() {
+		return errVal.Interface().(error)
+	}
+	return nil
+}