What is the common approach to reconstruct a domain model e.g. from a database model?

Given a domain model for a simplified todo app

type Todo struct {
    title          string
    isMarkedAsDone bool
    modifiedAt     time.Time
}

func NewTodo(title string) (*Todo, error) {
    if title == "" {
        return nil, errors.New("title is empty")
    }

    todo := &Todo{
        title:          title,
        isMarkedAsDone: false,
        modifiedAt:     time.Now(),
    }

    return todo, nil
}

func (t *Todo) GetTitle() string {
    return t.title
}

func (t *Todo) IsMarkedAsDone() bool {
    return t.isMarkedAsDone
}

// other getters...

func (t *Todo) Rename(newTitle string) error {
    if t.isMarkedAsDone {
        return errors.New("todo is already marked as done")
    }

    if newTitle == "" {
        return errors.New("new title is empty")
    }

    t.title = newTitle
    t.modifiedAt = time.Now()

    return nil
}

func (t *Todo) MarkAsDone() error {
    if t.isMarkedAsDone {
        return errors.New("todo is already marked as done")
    }

    t.isMarkedAsDone = true
    t.modifiedAt = time.Now()

    return nil
}

// other setters...

Saving this todo to a store is no problem since I can access the fields via getters. But when I query the store for todos that are marked as done I can’t reconstruct a domain object from the returned data.

The constructor function takes no isMarkedAsDone = true parameter ( + isMarkedAsDone ) and if I would try to create a new domain todo and call the MarkAsDone function on it I would overwrite the field modifiedAt which is wrong then.

What is a common approach to solve this?

  • Make everything public? ( feels wrong to me, consumers could put the domain object in invalid state )
  • Change the whole constructor function to accept all fields and validate all of them, so consumers have to provide all fields from outside?
  • Keep everything as is but provide a reconstruct function in the same package accepting all fields ( + validation ) that creates a domain model and has write access to the private fields since it lives in the same package?

The idiom for this kinf of problem in Go is use optional Pattern. Please check

And in some cases i aldo apply Builder pattern

Thanks for your reply. But how would you handle the modifiedAt field? Having a WithModifiedAt-function?

Normally you would never have such a function because this field will be set on every modification. So if you go for the options pattern you could also setup one big function that validates every parameter, no?

Yes, it does not need a getter/setter and create an option function per field

True, but when calling a function like Rename or MarkAsDone on Todo you would still update the modifiedAt field, right?

What if consumers then call WithModifiedAt again on it? I personally think they shouldn’t be able to do so …

This is the best option imo.

Making all fields public violates encapsulation and accepting all fields in constructor can lead to misusing it.