1
0
mirror of https://github.com/go-micro/go-micro.git synced 2026-05-06 19:21:46 +02:00
Files
Asim Aslam 1bb25d6e7f Add agent platform showcase and refactor project structure (#2884)
* 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>
2026-03-05 11:21:41 +00:00

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
}