Mastering Value Objects in Go: A Practical DDD Guide

This article walks through applying Domain‑Driven Design in Go, focusing on value objects: their definition, immutability, implementation details, common pitfalls with pointers and serialization, practical code patterns, and how to model enums, offering concrete guidance for robust backend development.

ITPUB
ITPUB
ITPUB
Mastering Value Objects in Go: A Practical DDD Guide

Introduction

The author, a senior engineer from a large internet company, launches a multi‑part series that shows how to bring Domain‑Driven Design (DDD) into real Go projects. The goal is to lower the steep learning curve of DDD by providing a complete, actionable methodology.

What Is a Value Object?

In DDD, a value object groups related attributes into a single conceptual whole and uses the ubiquitous language of the domain to make implicit concepts explicit. Properly modeled value objects improve code clarity, readability, and reduce bugs.

Key Characteristics

Immutability : once created, a value object should never be mutated; any change requires creating a new instance.

No Identity : equality is based on the equality of all attributes, not on a unique identifier.

Strict Implementation Example

A monetary value is used to illustrate a rigorous implementation. The struct is exported ( type MonetaryValue struct { amount int64 }) but its fields are unexported to prevent external mutation. Construction is forced through a factory function NewMonetaryValue(amount int64) (*MonetaryValue, error) that validates parameters.

Because all fields are unexported, the standard encoding/json package would ignore them, so a more serialization‑friendly version is shown where fields are exported or custom Marshal/Unmarshal methods are provided.

MonetaryValue implementation
MonetaryValue implementation

Handling Pointers, Slices, and Maps

When a value object contains reference types (pointers, slices, maps), external code can still modify the underlying data, breaking immutability. The article shows a BadDemo example where a slice field is shared, leading to unexpected side effects.

BadDemo with slice
BadDemo with slice

The recommended practice is to avoid reference types inside value objects or to copy them defensively.

Modifying via New Instances

Instead of mutating fields, methods return a new value object. An Add method on MonetaryValue demonstrates this pattern: it receives the value by value (not pointer) and returns a freshly constructed instance with the updated amount.

Add method returning new object
Add method returning new object

Why Immutability Matters

A multiplayer game scenario illustrates the danger of shared mutable state: if multiple players share the same value object and one player’s score is increased by directly modifying the object, all players see the change, causing incorrect game logic. Returning new instances prevents this.

Incorrect shared state
Incorrect shared state

Enum as a Value Object

Enums are treated as specialized value objects. The article presents a robust enum implementation with a zero value, validation slice, and factory functions, ensuring only valid enum values can be created.

Enum definition
Enum definition

A lighter approach using raw types (e.g., type AnyStatus int) is also shown, but it lacks compile‑time safety and requires runtime checks, which can lead to invalid values propagating through the domain.

Raw enum usage
Raw enum usage

Best‑Practice Checklist

Create value objects via simple factory functions to enforce invariants.

Never modify fields directly; always return a new instance.

Avoid embedding pointers, slices, or maps unless you defensively copy them.

Provide an Equals method for value comparison.

Treat primitive types like int64 or string as the simplest value objects.

Conclusion

Value objects are the building blocks of DDD in Go. Ensuring their immutability and proper construction leads to clearer, safer code and paves the way for modeling more complex domain concepts such as entities and aggregate roots.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Backend ArchitectureGoenumbest practicesimmutabilityDomain-Driven DesignValue Object
ITPUB
Written by

ITPUB

Official ITPUB account sharing technical insights, community news, and exciting events.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.