Golang / GoLang System Architecture and Testing Interview Questions
How do you implement event-driven communication between Go microservices using message queues?
Synchronous RPC (REST/gRPC) creates tight coupling β if service B is down, service A fails. Message queues (Kafka, NATS, RabbitMQ) decouple producers from consumers: A publishes an event and continues; B processes it when ready. This improves resilience and enables fan-out.
// NATS JetStream producer import "github.com/nats-io/nats.go" type EventPublisher struct { js nats.JetStreamContext } type UserCreatedEvent struct { UserID int `json:"user_id"` Email string `json:"email"` CreatedAt time.Time `json:"created_at"` } func (p *EventPublisher) PublishUserCreated(ctx context.Context, e UserCreatedEvent) error { data, err := json.Marshal(e) if err != nil { return fmt.Errorf("marshal event: %w", err) } msg := &nats.Msg{ Subject: "users.created", Data: data, Header: make(nats.Header), } // Propagate trace context in headers otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(msg.Header)) _, err = p.js.PublishMsg(msg) return err } // Consumer with idempotency type EmailConsumer struct { email EmailSender dedup *DeduplicationCache // prevent double-sending } func (c *EmailConsumer) HandleUserCreated(msg *nats.Msg) { var event UserCreatedEvent if err := json.Unmarshal(msg.Data, &event); err != nil { msg.Nak() // negative ack: requeue return } // Idempotency check Γ’ΒΒ process each event exactly once key := fmt.Sprintf("email:welcome:%d", event.UserID) if c.dedup.Has(key) { msg.Ack() // already processed Γ’ΒΒ ack without re-sending return } if err := c.email.SendWelcome(event.Email); err != nil { msg.Nak() return } c.dedup.Set(key) msg.Ack() }
Exactly-once delivery: message queues provide at-least-once delivery (messages may be re-delivered on failure). Consumers must be idempotent β processing the same message twice produces the same result. Use a deduplication cache (Redis SET NX with TTL) keyed on the event ID.
More Related questions...