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.
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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
ITPUB
Official ITPUB account sharing technical insights, community news, and exciting events.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
