Introduction
Do you understand Domain-Driven Design (DDD)? I still haven’t been able to understand it completely.
Recently, I’ve been diving into books on Domain-Driven Design (DDD). In DDD, the concept of a Value Object appears. A Value Object primarily has the following characteristics (elements unrelated to the main topic of this article are intentionally omitted):
- Immutable
- Objects are considered equal when their values are equivalent
To satisfy the above specifications in Golang, the implementation would need to look something like this:
type Person struct {
name string
}
func NewPerson(name string) Person {
return Person{name: name}
}
func (o Person) Name() string {
return o.name
}
func (o Person) Equal(other Person) bool {
return o.Name() == other.Name()
}
Frankly speaking, implementing this kind of functionality is a hassle. Additionally, writing unit tests for Getter() or Equal() feels pointless. I often found myself wishing, "If only Golang had something like Kotlin’s value class or data class."
Generates value object code
The nao1215/vogen package is a library that automatically generates Value Object code with New(), Getter, and Equal() methods. The name stands for "Value Object Generator."
With this library, you write metadata for Value Objects in Golang, and based on that metadata, the code is automatically generated. The inspiration for this specification comes from shogo82148/myddlmaker (a library that generates DB DDL from metadata).
Intended Usage
The typical usage involves defining metadata in value_object/gen/main.go
and running go generate ./...
to generate the value_object/value_object.go
file. It is also possible to distribute the output across multiple files.
Below is an example implementation for value_object/gen/main.go.
package main
import (
"fmt"
"path/filepath"
"github.com/nao1215/vogen"
)
//go:generate go run main.go
func main() {
// Step 1: Create a Vogen instance with custom file path and package name.
// By default, the file path is "value_objects.go" and the package name is "vo".
gen, err := vogen.New(
vogen.WithFilePath(filepath.Join("testdata", "example_output.go")),
vogen.WithPackageName("vo_example"),
)
if err != nil {
fmt.Printf("Failed to create Vogen instance: %v\n", err)
return
}
// Step 2: Append the ValueObject definition
if err := gen.AppendValueObjects(
vogen.ValueObject{
StructName: "Person",
Fields: []vogen.Field{
{Name: "Name", Type: "string", Comments: []string{"Name is the name of the person."}},
{Name: "Age", Type: "int", Comments: []string{"Age is the age of the person."}},
},
Comments: []string{
"Person is a Value Object to describe the feature of vogen.",
"This is sample comment.",
},
},
// Use auto generated comments.
vogen.ValueObject{
StructName: "Address",
Fields: []vogen.Field{
{Name: "City", Type: "string"},
},
},
); err != nil {
fmt.Printf("Failed to append ValueObject: %v\n", err)
return
}
// Step 3: Generate the code
if err := gen.Generate(); err != nil {
fmt.Printf("Failed to generate code: %v\n", err)
return
}
}
In vogen.New()
, you can specify the file path and package name for the generated code, but these are optional. If omitted, a value_objects.go file will be generated under the vo
package by default.
vogen.ValueObject()
corresponds to the metadata. Comments for the struct and its fields are optional. If omitted, the output will include soulless English comments. For types, you can specify Defined Types (user-defined types), but in such cases, you must also provide the module path. Since Defined Types have not been tested yet, I’ve deliberately omitted them from the example code (tests are planned for later).
Auto-Generated Code
Below is the code automatically generated using the sample above:
// Code generated by vogen. DO NOT EDIT.
package vo_example
import (
"fmt"
)
// Person is a Value Object to describe the feature of vogen.
// This is sample comment.
type Person struct {
// Name is the name of the person.
name string
// Age is the age of the person.
age int
}
// NewPerson creates a new instance of Person.
func NewPerson(name string, age int) Person {
return Person{name: name, age: age}
}
// Name returns the name field.
func (o Person) Name() string {
return o.name
}
// Age returns the age field.
func (o Person) Age() int {
return o.age
}
// Equal checks if two Person objects are equal.
func (o Person) Equal(other Person) bool {
return o.Name() == other.Name() && o.Age() == other.Age()
}
// Address represents a value object.
type Address struct {
city string
}
// NewAddress creates a new instance of Address.
func NewAddress(city string) Address {
return Address{city: city}
}
// City returns the city field.
func (o Address) City() string {
return o.city
}
// Equal checks if two Address objects are equal.
func (o Address) Equal(other Address) bool {
return o.City() == other.City()
}
Why I Wrote This Article
I wanted to know whether functionality like the vogen package would be of interest to Golang users.
Top comments (0)