©Sergey Emelyanov 2025 | Alle Rechte vorbehalten
Beim Design von API-Modulen stellt sich oft die Frage: Wie lässt sich die Logik für optionale Parameter einer Struktur elegant handhaben? Eine effiziente Lösung kann die Benutzerfreundlichkeit des APIs erheblich steigern. In diesem Artikel zeige ich anhand eines Praxisbeispiels aus der CRM-Entwicklung, wie das Builder-Pattern dabei hilft, komplexe Objekte mit optionalen Feldern flexibel zu konstruieren.
Betrachten wir ein Rechnungsmodul in einem CRM-System. Die Rechnung (Invoice) enthält Pflichtfelder wie Total oder LineItems, aber auch optionale Felder wie Description oder Discount. Zusätzlich gibt es Validierungsregeln:
Ein klassischer Konstruktor wie NewInvoice() wird schnell unhandlich:
type Invoice struct {
ID *string
Subject string
AssignedUser *int
CreateTime time.Time
UpdatedTime time.Time
LineItems []item.LineItem
Total float64
}
// Traditioneller Konstruktor – problematisch bei Erweiterungen
func NewInvoice(id string, subject string, user int, items []item.LineItem) (*Invoice, error) {
// ...
}
Jede neue Anforderung führt zu geänderter Signatur – ein Albtraum für die Rückwärtskompatibilität!
Das Builder-Pattern isoliert die Objektkonstruktion von der Struktur selbst. Ein InvoiceBuilder übernimmt die schrittweise Konfiguration:
type InvoiceBuilder struct {
Invoice
}
func NewInvoiceBuilder() *InvoiceBuilder {
return &InvoiceBuilder{
Invoice: Invoice{
CreateTime: time.Now(),
UpdatedTime: time.Now(),
},
}
}
// Methoden für optionale Felder
func (b *InvoiceBuilder) WithID(id string) *InvoiceBuilder {
b.ID = &id
return b
}
func (b *InvoiceBuilder) WithAssignedUser(id int) *InvoiceBuilder {
b.AssignedUser = &id
return b
}
func (b *InvoiceBuilder) WithLineItems(items []item.LineItem) *InvoiceBuilder {
b.LineItems = items
b.Total = float64(len(items)) * 10.0 // Beispielberechnung
return b
}
// Finale Validierung und Erstellung
func (b *InvoiceBuilder) Build() (*Invoice, error) {
if b.ID == nil && b.Total >= 1 {
return nil, fmt.Errorf("ID ist pflicht, wenn Total ≥ 1")
}
if len(b.LineItems) == 0 {
return nil, fmt.Errorf("mindestens ein LineItem erforderlich")
}
// Logik für AssignedUser
if b.AssignedUser == nil {
b.AssignedUser = GetDefaultUser()
} else {
switch {
case *b.AssignedUser == 0:
b.AssignedUser = RandomUser()
case *b.AssignedUser < 0:
return nil, ValidationError("Ungültiger Benutzer")
}
}
return &b.Invoice, nil
}
Der Builder ermöglicht eine klare, lesbare API mit Methodenketten:
func main() {
builder := NewInvoiceBuilder()
invoice, err := builder.
WithID("INV-2023-001").
WithLineItems([]item.LineItem{ /* ... */ }).
WithAssignedUser(5).
Build()
if err != nil {
log.Fatal("Fehler:", err)
}
fmt.Printf("Rechnung erstellt: %+v\n", invoice)
}
Das Builder-Pattern ist ideal für komplexe Objekte mit vielen optionalen Parametern oder dynamischer Validierung. In der CRM-Entwicklung reduziert es die Fehleranfälligkeit und verbessert die Wartbarkeit des Codes.
Weiterführende Optimierungen:
Mit dem Builder-Pattern bleibt Ihr API auch bei wachsenden Anforderungen benutzerfreundlich und robust.
©Sergey Emelyanov 2025 | Alle Rechte vorbehalten