Fundamentals 15 min read

Why Factories Matter in Go DDD: Clean Object Creation and Validation

This article explains how introducing factories in Domain‑Driven Design with Go separates object creation logic from domain responsibilities, hides implementation details, enforces invariants, and improves validation, while showing practical code patterns and design‑pattern choices for simple and complex domain objects.

ITPUB
ITPUB
ITPUB
Why Factories Matter in Go DDD: Clean Object Creation and Validation

Why Factories Matter in DDD

In Domain‑Driven Design (DDD) the factory concept separates the responsibility of creating domain objects from the objects themselves, making business intent clearer and keeping the model clean.

Benefits of Using Factories

Hide creation details to prevent leakage of domain knowledge.

Ensure that invariants are satisfied during construction.

Help express the ubiquitous language of the business.

Analogous to a car factory that assembles a vehicle before a user can drive it, a DDD factory assembles a domain object before it is used.

Implementing Factories in Go

For simple objects a plain New function is enough. It should return the created object and an error so that invalid parameters can be rejected early.

type Product struct { /* fields */ }

func NewProduct(...) (*Product, error) { /* validation and construction */ }

The function must be atomic – it either returns a fully valid object or nil with an error.

Simple Factory Example

The MonetaryValue type is created with NewMonetaryValue. The first version returned a partially built value even when validation failed, forcing callers to check the object’s fields. The corrected version returns nil when the validation error is non‑nil.

func NewMonetaryValue(amount int) (*MonetaryValue, error) {
    if amount < 0 {
        return nil, errors.New("amount must be non‑negative")
    }
    return &MonetaryValue{Amount: amount}, nil
}

Complex Factory Example

When construction involves external services, a dedicated factory struct (or service) is used. The article shows a ProductEvaluationCreator that calls an anti‑cheat service before creating a ProductEvaluation entity.

type ProductEvaluationCreator struct { antiCheat AntiCheatService }

func (c *ProductEvaluationCreator) Create(ctx context.Context, data EvalData) (*ProductEvaluation, error) {
    if err := c.antiCheat.Check(data.Content); err != nil {
        return nil, err
    }
    return entity.NewEvaluation(data), nil
}

Model Validation Strategies

Validation can be placed inside the factory, inside setter methods, or in a separate validator component. Setter methods should return an error and preferably be unexported.

func (p *Product) SetAge(age int) error {
    if age < 18 || age > 60 {
        return errors.New("age out of range")
    }
    p.Age = age
    return nil
}

For objects with many fields, a dedicated validator struct can hold the instance and perform holistic checks.

type ProductValidator struct { product *Product }

func (v *ProductValidator) Validate() error { /* attribute and cross‑field checks */ }

When validation is simple, a plain function can be used instead of a struct.

Validation Levels

Attribute‑level validation (e.g., age range).

Object‑level validation (ensuring the whole entity satisfies business rules).

Composition‑level validation (checking relationships between multiple entities, often handled by domain services).

Example: an order service must verify both Product availability and Inventory stock before proceeding.

Design‑Pattern Choices for Factories

Various creational patterns can be applied in Go:

Simple Factory – a function returning different concrete types based on a parameter.

Factory Method – an interface‑based factory for better extensibility.

Abstract Factory – creates families of related objects.

Builder – assembles complex objects step by step.

Options – functional options pattern for many optional parameters.

For complex business processes the Builder and Options patterns often provide the most flexibility.

Conclusion

Introducing factories in DDD cleanly separates creation logic from domain behavior, guarantees that objects satisfy invariants, and makes the model easier to maintain. Simple objects can use a New function, while complex ones benefit from dedicated factory structs, validators, or builder‑style implementations.

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.

Software ArchitectureGoDDDFactory PatternDomain-Driven Design
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.