zoobzio January 24, 2026 Edit this page

sentinel Integration

slush integrates with sentinel to provide type metadata for registered services.

What sentinel Provides

sentinel is a struct introspection library that extracts metadata from Go types:

  • Field names, types, and tags
  • Type relationships (references, collections, embeddings)
  • Fully qualified domain names (FQDNs)

This metadata enables documentation generation, schema export, and ERD visualization.

How slush Uses sentinel

When you call Services(), slush looks up each implementation's metadata from sentinel's cache:

svcs := slush.Services()
for _, svc := range svcs {
    fmt.Printf("Interface: %s\n", svc.Interface)
    fmt.Printf("Impl: %s\n", svc.Impl)
    fmt.Printf("Fields: %d\n", len(svc.Metadata.Fields))
}

The Pipeline

Your App                    sentinel                 slush
────────                    ────────                 ─────

1. Define types
   type UserService struct {
       db Database
   }

2. Scan at startup  ──────► sentinel.Scan[UserService]()
                            (caches metadata)

3. Register service ──────────────────────────────────► Register[UserAPI](svc)
                                                        (stores impl FQDN)

4. Enumerate        ◄─────────────────────────────────  Services()
                            sentinel.Lookup(fqdn)       (returns ServiceInfo
                            (retrieves metadata)         with Metadata)

Setup

1. Scan your types

Before registering services, scan your domain types with sentinel:

func main() {
    // Scan root type - discovers related types
    sentinel.Scan[UserService]()
    sentinel.Scan[OrderService]()

    // Now register services
    slush.Register[UserAPI](userService)
    slush.Register[OrderAPI](orderService)
}

2. Query metadata

Use Services() to get metadata for all registered services:

func handleSchemaEndpoint(w http.ResponseWriter, r *http.Request) {
    svcs := slush.Services()

    schema := make(map[string]any)
    for _, svc := range svcs {
        schema[svc.Interface] = map[string]any{
            "impl":       svc.Impl,
            "fields":     svc.Metadata.Fields,
            "guards":     svc.GuardCount,
        }
    }

    json.NewEncoder(w).Encode(schema)
}

What slush Provides to sentinel

sentinel Needsslush Provides
Type FQDN for lookupServiceInfo.Impl (implementation FQDN)
Registration eventcapitan SignalRegistered with FQDN

ServiceInfo Fields

type ServiceInfo struct {
    Interface  string            // Interface FQDN (lookup key)
    Impl       string            // Implementation FQDN
    Metadata   sentinel.Metadata // From sentinel.Lookup()
    GuardCount int               // Number of guards
}

The Metadata field contains sentinel's extracted information:

type Metadata struct {
    FQDN          string
    TypeName      string
    PackageName   string
    Fields        []FieldMetadata
    Relationships []TypeRelationship
}

Example: ERD Generation

Combine slush and sentinel for service relationship visualization:

func generateERD() string {
    var buf strings.Builder
    buf.WriteString("graph LR\n")

    for _, svc := range slush.Services() {
        // Node for service
        buf.WriteString(fmt.Sprintf("  %s[%s]\n",
            svc.Metadata.TypeName,
            svc.Interface))

        // Edges for relationships
        for _, rel := range svc.Metadata.Relationships {
            buf.WriteString(fmt.Sprintf("  %s --> %s\n",
                svc.Metadata.TypeName,
                rel.To))
        }
    }

    return buf.String()
}

When Metadata Is Empty

ServiceInfo.Metadata will be empty if:

  1. Type wasn't scanned: Call sentinel.Scan[T]() before registration
  2. FQDN mismatch: sentinel and slush compute FQDNs the same way, but verify with sentinel.Browse()
  3. Interface type: sentinel scans structs, not interfaces. The impl FQDN must be a struct.

Debug with:

// What sentinel has cached
for _, fqdn := range sentinel.Browse() {
    fmt.Println(fqdn)
}

// What slush is looking for
for _, svc := range slush.Services() {
    fmt.Printf("Looking for: %s\n", svc.Impl)
}

Next Steps