go-ddd icon indicating copy to clipboard operation
go-ddd copied to clipboard

What's purpose of constructors?

Open cl1ckname opened this issue 1 year ago • 2 comments

For example

func NewProduct(name string, price float64, seller ValidatedSeller) *Product {
	return &Product{
		Id:        uuid.New(),
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
		Name:      name,
		Price:     price,
		Seller:    seller.Seller,
	}
}
  • You're using public struct fields
  • This constructor isn't clean
  • This constructor doesn't validate input data

So that's means

  • I can init object without calling constructor
  • I can't use this constructor in my tests, because I don't know what value of id and created at I've should to expect
  • I can violate the domain invariant (obviously price is always positive) and don't notice it

In my mind to initialize entities you should use fabrics and validate values before set it.

cl1ckname avatar Nov 18 '24 21:11 cl1ckname

In general, constructors (factory functions) in your domain serve three main purposes:

Centralize and enforce invariants:

  • Every time you create a Product, you want to ensure it has a valid name, positive price, generated ID and timestamps
  • By putting all that logic into NewProduct(…), you guarantee nobody ever forgets to check “price > 0” or to set CreatedAt.

Encapsulate defaults and side‐effects:

  • UUID‐generation, time.Now(), any domain‐specific defaults all live in one place.
  • Tests don’t have to reinvent that logic; they just call the constructor (or a test‐helper variant) and get a fully formed entity.

Separate rehydration from creation. You typically have two factories:

  • NewProduct(...) (*Product,error) – for writes, enforces validation, sets defaults, returns error on bad input
  • ReconstituteProduct(id, name, price, seller, createdAt, updatedAt) – for reads (ORM/repo), skips validation and defaults so you can load historical data without error.

Constructors are your gatekeepers for consistency and public fields don’t trump your factory “contract.”

sklinkert avatar Apr 28 '25 06:04 sklinkert

I write my comment not about concept of the constructors, but about constructors in this repository. Of course, we can validate for e.g. price in factory function, but it is not shown here. We can hide side-effect defaults like Now() of UUID in factories, behind interfaces or types, but it is not shown here. We can separate creation and restoration, but it is not shown here.

cl1ckname avatar Apr 28 '25 18:04 cl1ckname