Lesson 8 · Methods, interfaces & generics

Composition & embedding

Go's answer to inheritance — reuse by having, not by being.

Your win: explain embedding and method promotion, say why it's composition rather than inheritance, and read the four ways our repo uses it.

Embedding = a field with no name

Declare a type inside a struct without a field name, and its exported fields and methods are promoted to the outer type — callers use them as if they belonged to the outer struct.1

type commonConsumer struct { ... }
func (c *commonConsumer) handleMessage(...) (bool, error) { ... }

type looseConsumer struct {
    commonConsumer          // embedded — no field name
}
// looseConsumer now HAS handleMessage(), promoted from commonConsumer.
Composition, not inheritance Embedding is "has-a with promotion", not "is-a". There's no subclass, no virtual dispatch, no base-class coupling. The outer type reuses the inner's behaviour and can add or shadow methods — but it never magically overrides them the way OOP inheritance does.

Your repo embeds in four distinct ways

KindExample in our codeWhy
Struct-in-struct (share behaviour) commonConsumer in looseConsumer/strictConsumer golibs/kafka/consumer.go:166,204 both consumer types reuse retry/handle logic, differ only on commit
Interface-in-struct consumers.ConsumerHandler in SendEmailConsumer spike/.../kafka/send_email_consumer.go:14 the struct "is a" handler by delegating to whatever's injected
Multiple embeds (value + pointer) jwt.Claims, *FirebaseClaims, … in CustomClaims golibs/interceptors/auth_claims.go:13 merge several claim shapes into one type
Interface composition AllServicer[T] embeds BaseServicer[T] + 5 more golibs/bootstrap/grpc.go:35 build a big capability from small interfaces
The one you'll see constantly Every gRPC service embeds npb.UnimplementedNotificationReaderServiceServer (notification/transports/grpc/notificationmgmt_v1.go:24). That embed supplies default "not implemented" methods, so when a new RPC is added to the proto your server still compiles — forward compatibility via embedding.

Interface embedding composes contracts

Interfaces embed too — that's how the standard library builds io.ReadWriter from io.Reader + io.Writer, and how our AllServicer[T] is assembled from BaseServicer, GRPCServicer, KafkaServicer, and friends. Small interfaces (Lesson 6) compose into bigger capabilities without a single large declaration.

Read this next

Effective Go — Embedding

The canonical explanation of promotion and why Go chose composition. GOPL ch. 6 goes deeper with worked examples.

go.dev/doc/effective_go#embedding
The Go Programming Language — ch. 6 (methods)

Check yourself (from memory)

Q1. Embedding a type in a struct (no field name)…

Promotion: the outer type gets the inner's exported fields/methods, callable directly. That's how looseConsumer reuses commonConsumer.

Q2. Go's embedding is best described as…

Has-a with promotion. No base class, no virtual dispatch — the outer type reuses and can shadow, not override.

Q3. Embedding Unimplemented...Server in a gRPC service gives…

Default "unimplemented" methods, so adding an RPC to the proto doesn't break the build. Forward compatibility via embedding.
How does looseConsumer reuse commonConsumer's behaviour without inheritance?
recall, then click to reveal
It embeds commonConsumer as an unnamed field, so its methods (like handleMessage) are promoted onto looseConsumer — it "has" them, no class hierarchy. looseConsumer then adds only what differs (its commit behaviour). Composition + promotion, not inheritance.
Want to see method shadowing (outer type defines a method with the same name as the embedded one) and how ambiguous promotions resolve? Ask me.

1. Effective Go — embedding; Go spec — struct types (embedded fields).