goca entity
Generate pure domain entities following Domain-Driven Design (DDD) principles.
Syntax
goca entity <EntityName> [flags]Description
The goca entity command generates domain entities that represent core business concepts. These entities are pure, containing only business logic without external dependencies.
Domain Layer
Entities are the heart of Clean Architecture - they contain enterprise-wide business rules and are completely independent of frameworks, databases, or external systems.
Arguments
<EntityName>
Required. The name of your entity (singular, PascalCase).
goca entity User
goca entity Product
goca entity OrderFlags
--fields
Required. Define the structure of your entity.
Format: "field1:type1,field2:type2,..."
Supported Types:
string- Text dataint,int64- Integer numbersfloat64- Decimal numbersbool- Boolean valuestime.Time- Timestamps[]type- Arrays/slices
goca entity Product --fields "name:string,price:float64,stock:int"--validation
Include domain-level validation methods.
goca entity User --fields "name:string,email:string,age:int" --validationGenerates:
Validate()method with business rules- Domain-specific error constants
- Input sanitization
--business-rules
Generate business rule methods.
goca entity Order --fields "total:float64,status:string" --business-rulesGenerates methods like:
CanBeCancelled() boolIsCompleted() bool- Business logic calculations
--timestamps
Add automatic timestamp fields.
goca entity Product --fields "name:string,price:float64" --timestampsAdds:
CreatedAt time.TimeUpdatedAt time.Time
--soft-delete
Enable soft delete functionality.
goca entity User --fields "name:string,email:string" --soft-deleteAdds:
DeletedAt *time.TimeIsDeleted() boolmethod
--tests
Generate unit tests for the entity (enabled by default).
goca entity User --fields "name:string,email:string,age:int" --validation --testsGenerates: internal/domain/user_test.go
Creates comprehensive test suite including:
- Validation tests: Table-driven tests for
Validate()method - Initialization tests: Verify field assignment
- Edge case tests: Test boundary conditions for each field
- Type-specific tests: String length, numeric ranges, email format, etc.
Disable tests:
goca entity User --fields "name:string,email:string" --tests=falseTesting Best Practices
Generated tests use testify/assert for readable assertions and follow table-driven test patterns recommended by the Go community.
Examples
Basic Entity
goca entity User --fields "name:string,email:string,age:int"Generates: internal/domain/user.go
package domain
import "time"
type User struct {
ID uint `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
Age int `json:"age"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
func NewUser(name, email string, age int) *User {
return &User{
Name: name,
Email: email,
Age: age,
}
}Entity with Validation
goca entity Product \
--fields "name:string,price:float64,stock:int" \
--validationGenerates:
package domain
import (
"errors"
"strings"
)
type Product struct {
ID uint `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
Stock int `json:"stock"`
}
func (p *Product) Validate() error {
if strings.TrimSpace(p.Name) == "" {
return ErrProductNameRequired
}
if p.Price < 0 {
return ErrInvalidPrice
}
if p.Stock < 0 {
return ErrInvalidStock
}
return nil
}
var (
ErrProductNameRequired = errors.New("product name is required")
ErrInvalidPrice = errors.New("price cannot be negative")
ErrInvalidStock = errors.New("stock cannot be negative")
)Complete Entity
goca entity Order \
--fields "customerID:int,total:float64,status:string" \
--validation \
--business-rules \
--timestamps \
--soft-deleteGenerates:
package domain
import (
"errors"
"time"
)
type Order struct {
ID uint `json:"id"`
CustomerID int `json:"customer_id"`
Total float64 `json:"total"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `json:"deleted_at,omitempty"`
}
func (o *Order) Validate() error {
if o.CustomerID <= 0 {
return ErrInvalidCustomerID
}
if o.Total < 0 {
return ErrInvalidTotal
}
if !o.IsValidStatus() {
return ErrInvalidStatus
}
return nil
}
// Business Rules
func (o *Order) IsValidStatus() bool {
validStatuses := []string{"pending", "processing", "completed", "cancelled"}
for _, status := range validStatuses {
if o.Status == status {
return true
}
}
return false
}
func (o *Order) CanBeCancelled() bool {
return o.Status == "pending" || o.Status == "processing"
}
func (o *Order) IsCompleted() bool {
return o.Status == "completed"
}
func (o *Order) IsDeleted() bool {
return o.DeletedAt != nil
}
var (
ErrInvalidCustomerID = errors.New("invalid customer ID")
ErrInvalidTotal = errors.New("total cannot be negative")
ErrInvalidStatus = errors.New("invalid order status")
)Entity with Unit Tests
goca entity User \
--fields "name:string,email:string,age:int" \
--validation \
--testsGenerates: internal/domain/user_test.go
package domain
import (
"testing"
"github.com/stretchr/testify/assert"
)
// TestUser_Validate tests the Validate method with various scenarios
func TestUser_Validate(t *testing.T) {
tests := []struct {
name string
user User
wantErr bool
errMsg string
}{
{
name: "valid entity",
user: User{
Name: "John Doe",
Email: "test@example.com",
Age: 25,
},
wantErr: false,
},
{
name: "invalid user - empty name",
user: User{
Name: "",
Email: "test@example.com",
Age: 25,
},
wantErr: true,
errMsg: "name",
},
{
name: "invalid user - negative age",
user: User{
Name: "John Doe",
Email: "test@example.com",
Age: -1,
},
wantErr: true,
errMsg: "age",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.user.Validate()
if tt.wantErr {
assert.Error(t, err)
if tt.errMsg != "" {
assert.Contains(t, err.Error(), tt.errMsg)
}
} else {
assert.NoError(t, err)
}
})
}
}
// TestUser_Initialization tests entity field initialization
func TestUser_Initialization(t *testing.T) {
user := &User{
Name: "John Doe",
Email: "test@example.com",
Age: 25,
}
assert.Equal(t, "John Doe", user.Name, "Name should be set correctly")
assert.Equal(t, "test@example.com", user.Email, "Email should be set correctly")
assert.Equal(t, 25, user.Age, "Age should be set correctly")
}
// Additional field-specific tests for edge cases...Run tests:
cd internal/domain
go test -v -run TestUserExpected output:
=== RUN TestUser_Validate
=== RUN TestUser_Validate/valid_entity
=== RUN TestUser_Validate/invalid_user_-_empty_name
=== RUN TestUser_Validate/invalid_user_-_negative_age
--- PASS: TestUser_Validate (0.00s)
=== RUN TestUser_Initialization
--- PASS: TestUser_Initialization (0.00s)
PASS
ok yourproject/internal/domain 0.013sGenerated Structure
internal/
└── domain/
├── order.go # Main entity
├── order_test.go # Unit tests (if --tests)
├── order_seeds.go # Seed data
└── errors.go # Domain errors (if --validation)Note
The --tests flag is enabled by default. All generated tests use table-driven patterns and testify/assert for clean, maintainable test code.
Best Practices
DO
- Keep entities pure (no external dependencies)
- Include business validations
- Add meaningful business rule methods
- Use value objects for complex types
- Document business logic
Example:
// Good: Pure domain logic
func (u *User) IsAdult() bool {
return u.Age >= 18
}
func (u *User) CanPlaceOrder() bool {
return u.IsActive && u.EmailVerified
}DON'T
- Import database packages
- Import HTTP frameworks
- Include infrastructure logic
- Add persistence methods
Example:
// Bad: Infrastructure dependency
import "database/sql"
func (u *User) Save(db *sql.DB) error {
// Wrong layer!
}
// Bad: Framework dependency
import "github.com/gin-gonic/gin"
func (u *User) ToJSON(c *gin.Context) {
// Wrong responsibility!
}Integration
After generating an entity, you typically:
Generate Use Cases:
bashgoca usecase OrderService --entity OrderGenerate Repository:
bashgoca repository Order --database postgresGenerate Handler:
bashgoca handler Order --type http
Or use the complete feature command:
goca feature Order --fields "customerID:int,total:float64,status:string"Field Type Reference
| Type | Description | Example |
|---|---|---|
string | Text data | "name:string" |
int | Integer | "age:int" |
int64 | Large integer | "userID:int64" |
float64 | Decimal number | "price:float64" |
bool | Boolean | "isActive:bool" |
time.Time | Timestamp | "birthDate:time.Time" |
[]string | String array | "tags:[]string" |
[]int | Integer array | "scores:[]int" |
Tips
Naming Conventions
# Good
goca entity User --fields "firstName:string,lastName:string"
goca entity Product --fields "productName:string,unitPrice:float64"
# Avoid
goca entity user --fields "first_name:string" # Use PascalCase
goca entity PRODUCT --fields "PRICE:float64" # Too loudComplex Domains
For complex domains, start simple and add complexity:
# Step 1: Basic structure
goca entity Invoice --fields "number:string,amount:float64"
# Step 2: Add more fields manually
# Edit internal/domain/invoice.go to add:
# - items []InvoiceItem
# - taxes []Tax
# - metadata map[string]interface{}Troubleshooting
Entity Already Exists
Problem: "entity already exists"
Solution:
# Remove existing entity
rm internal/domain/order.go
rm internal/domain/order_*.go
# Regenerate
goca entity Order --fields "..."Invalid Field Type
Problem: "unsupported field type"
Solution: Use basic types first, then manually add custom types:
// After generation, manually add:
type User struct {
// ... generated fields ...
Address Address // Custom type
Tags []Tag // Custom slice
}See Also
goca feature- Generate complete featuregoca usecase- Generate use casesgoca repository- Generate repositories- Clean Architecture Guide - Architecture principles
- Domain Layer - Layer details
Next Steps
After creating your entity:
- Add business logic methods manually if needed
- Generate use cases that use this entity
- Create repositories for persistence
- Build handlers for external access
Or use the shortcut:
goca feature YourEntity --fields "..." # Generates everything!