Infrastructure Go daemon
SECTION 2.6

Composition Over Inheritance

Every Kubernetes resource — Pod, Service, ConfigMap, Deployment — embeds metav1.ObjectMeta to share Name, Namespace, and Labels. That single embed is what lets one controller iterate any resource type. Same mechanic, your own types.

Go has no inheritance. Instead, you embed one struct inside another. The embedded struct's fields and methods get "promoted" — they're accessible directly on the outer struct.

1 · Before you read — commit to an answer
type Metadata struct{ Name string }

func (m Metadata) FullName() string { return "prod/" + m.Name }

type Pod struct { Metadata Status string }

func main() { p := Pod{Metadata: Metadata{Name: "web-1"}, Status: "Running"} fmt.Println(p.Name) fmt.Println(p.FullName()) }

Note that Pod defines neither a Name field nor a FullName method. Wrong is fine — promotion is the whole point of this section.

Embedding

2 · Worked example — read every step
type Metadata struct {
    Name      string
    Namespace string
    Labels    map[string]string
}

func (m Metadata) FullName() string {
    return m.Namespace + "/" + m.Name
}

type Pod struct {
    Metadata           // embedded — no field name
    Status   string
    Resources Resources
}

type Service struct {
    Metadata          // same Metadata, different resource
    Port     int
    Target   int
}

// Promoted fields — access directly
pod := Pod{
    Metadata: Metadata{
        Name:      "web-1",
        Namespace: "production",
        Labels:    map[string]string{"app": "web"},
    },
    Status: "Running",
}

fmt.Println(pod.Name)       // "web-1" — promoted from Metadata
fmt.Println(pod.FullName()) // "production/web-1" — promoted method

This is how the real Kubernetes Go types work. ObjectMeta is embedded in every resource type.

3 · Fill the gaps
Give ConfigMap the same name plumbing — one embed, zero forwarding methods.
type ConfigMap struct {
                // no field name — that's the embed
    Data map[string]string
}

cm := ConfigMap{ : Metadata{Name: "app-config", Namespace: "prod"}, Data: map[string]string{"LOG_LEVEL": "debug"}, }

fmt.Println(cm.) // "prod/app-config" fmt.Println(cm.Namespace) // "prod" — promoted field

Why Composition

// Python/Java instinct: class Pod(Resource) — inheritance
// Go: embed the shared parts, compose the rest

// This lets you:
// 1. Share fields/methods without a type hierarchy
// 2. Embed multiple types (no diamond problem)
// 3. Override promoted methods by defining your own

Interface Satisfaction Travels Through Embedding

This is the move that makes Kubernetes-style code work. Define an interface once, and any type that embeds something which already satisfies it gets the interface for free:

type Named interface {
    FullName() string
}

// Pod embeds Metadata. Metadata has FullName(). So Pod satisfies Named —
// without Pod ever defining FullName itself.
var n Named = Pod{Metadata: Metadata{Name: "web-1", Namespace: "prod"}}
fmt.Println(n.FullName()) // "prod/web-1"

// Service embeds Metadata too. Same deal.
var n2 Named = Service{Metadata: Metadata{Name: "web-svc", Namespace: "prod"}}

Real Kubernetes types do exactly this. Every resource type — Pod, Deployment, Service, ConfigMap — embeds metav1.ObjectMeta, which carries Name, Namespace, Labels, Annotations, UID, ResourceVersion, and the methods GetName(), GetNamespace(), etc. The metav1.Object interface is satisfied by every K8s resource via that single embed. When a controller iterates a list of runtime.Object, that's the mechanism.

Overriding a Promoted Method

If the outer type defines its own version of a promoted method, the outer wins:

type LoggingPod struct {
    Pod
}

func (LoggingPod) FullName() string {
    return "[logged] pod" // shadows Pod.FullName / Metadata.FullName
}

You can still reach the inner one explicitly: lp.Pod.FullName().

Ambiguous Embedded Methods

Embedding two types that both have the same method name is the one Go gotcha here:

type A struct{}
func (A) Name() string { return "from A" }

type B struct{}
func (B) Name() string { return "from B" }

type Both struct {
    A
    B
}

var x Both
// x.Name() // ambiguous — won't compile
fmt.Println(x.A.Name(), x.B.Name()) // explicit access works

The compiler refuses to pick — you have to disambiguate by selecting x.A.Name() or x.B.Name() directly. This is why the metav1 types embed only one anchor (ObjectMeta) rather than mixing in multiple sources of name/namespace.

4 · From scratch — this feeds your review queue