mirror of
https://github.com/go-micro/go-micro.git
synced 2026-05-06 19:21:46 +02:00
1bb25d6e7f
* feat: add agent platform showcase and blog post Add a complete platform example (Users, Posts, Comments, Mail) that mirrors micro/blog, demonstrating how existing microservices become AI-accessible through MCP with zero code changes. Includes blog post "Your Microservices Are Already an AI Platform" walking through real agent workflows: signup, content creation, commenting, tagging, and cross-service messaging. https://claude.ai/code/session_01GkduEhcrqcG45rdfYh8dAc * refactor: rename handler types to drop redundant Service suffix UserService → Users, PostService → Posts, CommentService → Comments, MailService → Mail. Matches micro/blog naming convention. https://claude.ai/code/session_01GkduEhcrqcG45rdfYh8dAc * refactor: consolidate top-level directories, reduce framework bloat Move internal/non-public packages behind internal/ or into their parent packages where they belong: - deploy/ → gateway/mcp/deploy/ (Helm charts belong with the gateway) - profile/ → service/profile/ (preset plugin profiles are a service concern) - scripts/ → internal/scripts/ (install script is not public API) - test/ → internal/test/ (test harness is not public API) - util/ → internal/util/ (internal helpers shouldn't be imported externally) Also fixes CLAUDE.md merge conflict markers and updates project structure documentation. All import paths updated. Build and tests pass. https://claude.ai/code/session_01GkduEhcrqcG45rdfYh8dAc * refactor: redesign model package to match framework conventions Rename model.Database interface to model.Model (consistent with client.Client, server.Server, store.Store). Remove generics in favor of interface{}-based API with reflection. Key changes: - model.Model interface: Register once, CRUD infers table from type - DefaultModel + NewModel() + package-level convenience functions - Schema registered via Register(&User{}), no per-call schema passing - Memory implementation as default (in model package, like store) - memory/sqlite/postgres backends updated for new interface - protoc-gen-micro generates RegisterXModel() instead of generic factory - All docs, blog, and README updated https://claude.ai/code/session_01GkduEhcrqcG45rdfYh8dAc --------- Co-authored-by: Claude <noreply@anthropic.com>
163 lines
3.6 KiB
Go
163 lines
3.6 KiB
Go
package model
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
)
|
|
|
|
// Schema describes a model's storage layout, derived from struct tags.
|
|
type Schema struct {
|
|
// Table name in the database.
|
|
Table string
|
|
// Key is the name of the primary key field.
|
|
Key string
|
|
// Fields maps Go field names to their column metadata.
|
|
Fields []Field
|
|
}
|
|
|
|
// Field describes a single field in the schema.
|
|
type Field struct {
|
|
// Name is the Go struct field name.
|
|
Name string
|
|
// Column is the database column name (from json tag or lowercased name).
|
|
Column string
|
|
// Type is the Go reflect type.
|
|
Type reflect.Type
|
|
// IsKey indicates this is the primary key field.
|
|
IsKey bool
|
|
// Index indicates this field should be indexed.
|
|
Index bool
|
|
}
|
|
|
|
// BuildSchema extracts a Schema from a struct type using reflection.
|
|
func BuildSchema(v interface{}, opts ...RegisterOption) *Schema {
|
|
t := reflect.TypeOf(v)
|
|
if t.Kind() == reflect.Ptr {
|
|
t = t.Elem()
|
|
}
|
|
|
|
schema := &Schema{
|
|
Table: strings.ToLower(t.Name()) + "s",
|
|
}
|
|
|
|
for i := 0; i < t.NumField(); i++ {
|
|
f := t.Field(i)
|
|
if !f.IsExported() {
|
|
continue
|
|
}
|
|
|
|
field := Field{
|
|
Name: f.Name,
|
|
Type: f.Type,
|
|
}
|
|
|
|
// Column name: use json tag if present, else lowercase field name
|
|
if tag := f.Tag.Get("json"); tag != "" {
|
|
parts := strings.Split(tag, ",")
|
|
if parts[0] != "" && parts[0] != "-" {
|
|
field.Column = parts[0]
|
|
}
|
|
}
|
|
if field.Column == "" {
|
|
field.Column = strings.ToLower(f.Name)
|
|
}
|
|
|
|
// Check model tag
|
|
if tag := f.Tag.Get("model"); tag != "" {
|
|
for _, opt := range strings.Split(tag, ",") {
|
|
switch opt {
|
|
case "key":
|
|
field.IsKey = true
|
|
schema.Key = field.Column
|
|
case "index":
|
|
field.Index = true
|
|
}
|
|
}
|
|
}
|
|
|
|
schema.Fields = append(schema.Fields, field)
|
|
}
|
|
|
|
if schema.Key == "" {
|
|
// Default to "id" if no key tag found
|
|
for i := range schema.Fields {
|
|
if schema.Fields[i].Column == "id" {
|
|
schema.Fields[i].IsKey = true
|
|
schema.Key = "id"
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, o := range opts {
|
|
o(schema)
|
|
}
|
|
|
|
return schema
|
|
}
|
|
|
|
// StructToMap converts a struct pointer to a map of column name → value.
|
|
func StructToMap(schema *Schema, v interface{}) map[string]any {
|
|
rv := reflect.ValueOf(v)
|
|
if rv.Kind() == reflect.Ptr {
|
|
rv = rv.Elem()
|
|
}
|
|
fields := make(map[string]any, len(schema.Fields))
|
|
for _, f := range schema.Fields {
|
|
fv := rv.FieldByName(f.Name)
|
|
if fv.IsValid() {
|
|
fields[f.Column] = fv.Interface()
|
|
}
|
|
}
|
|
return fields
|
|
}
|
|
|
|
// MapToStruct fills a struct pointer from a map of column name → value.
|
|
func MapToStruct(schema *Schema, fields map[string]any, v interface{}) {
|
|
rv := reflect.ValueOf(v)
|
|
if rv.Kind() == reflect.Ptr {
|
|
rv = rv.Elem()
|
|
}
|
|
for _, f := range schema.Fields {
|
|
val, ok := fields[f.Column]
|
|
if !ok {
|
|
continue
|
|
}
|
|
fv := rv.FieldByName(f.Name)
|
|
if !fv.IsValid() || !fv.CanSet() {
|
|
continue
|
|
}
|
|
rval := reflect.ValueOf(val)
|
|
if rval.Type().AssignableTo(fv.Type()) {
|
|
fv.Set(rval)
|
|
} else if rval.Type().ConvertibleTo(fv.Type()) {
|
|
fv.Set(rval.Convert(fv.Type()))
|
|
}
|
|
}
|
|
}
|
|
|
|
// NewFromSchema creates a new zero-value struct pointer for the given schema's original type.
|
|
func NewFromSchema(schema *Schema, rtype reflect.Type) interface{} {
|
|
return reflect.New(rtype).Interface()
|
|
}
|
|
|
|
// KeyValue extracts the key value from a struct using the schema.
|
|
func KeyValue(schema *Schema, v interface{}) string {
|
|
fields := StructToMap(schema, v)
|
|
key, ok := fields[schema.Key]
|
|
if !ok {
|
|
return ""
|
|
}
|
|
return fmt.Sprint(key)
|
|
}
|
|
|
|
// ResolveType returns the struct reflect.Type from a value (handles pointers and slices).
|
|
func ResolveType(v interface{}) reflect.Type {
|
|
t := reflect.TypeOf(v)
|
|
for t.Kind() == reflect.Ptr || t.Kind() == reflect.Slice {
|
|
t = t.Elem()
|
|
}
|
|
return t
|
|
}
|