©Sergey Emelyanov 2025 | Alle Rechte vorbehalten
Go 1.18 hat Generics eingeführt – ein mächtiges Werkzeug, um typsicheren, wiederverwendbaren Code zu schreiben. In diesem Artikel zeigen wir, wie wir mit Generics redundanten Datenbank-Code refaktoriert haben.
In unserem Projekt EasyList hatten wir zwei Structs (Folder und Item), die ähnliche Insert
-Methoden für die DB verwendeten:
type Folder struct {
ID int64 `jsonapi:"primary,folders"`
Name string `jsonapi:"attr,name"`
// ... weitere Felder
}
type Item struct {
ID int64 `jsonapi:"primary,items"`
UserId int64 `json:"-"`
// ... weitere Felder
}
Die ursprünglichen Insert-Methoden:
// Insert für Folder
func (f FolderModel) Insert(folder *Folder) error {
result, err := f.DB.ExecContext(ctx, query, args...)
id, _ := result.LastInsertId()
folder.ID = id
// ...
}
// Insert für Item
func (i ItemModel) Insert(item *Item) error {
result, err := i.DB.ExecContext(ctx, query, args...)
id, _ := result.LastInsertId()
item.ID = id
// ...
}
Problem: Beide Methoden waren zu 90% identisch – ein klarer Fall für Refactoring!
Da Go keine direkte Feldzuweisung in Generics erlaubt, definieren wir ein Interface:
type Entity interface {
SetId(id int64)
}
// Implementierung für Folder
func (f *Folder) SetId(id int64) {
f.ID = id
}
// Implementierung für Item
func (i *Item) SetId(id int64) {
i.ID = id
}
func Insert[T Entity](db *sql.DB, entity T, query string, args ...any) (T, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
result, err := db.ExecContext(ctx, query, args...)
if err != nil {
return entity, err
}
id, err := result.LastInsertId()
if err != nil {
return entity, err
}
entity.SetId(id) // ID via Interface zuweisen
return entity, nil
}
Folder
:func (f FolderModel) Insert(folder *Folder) error {
query := `INSERT INTO folders (...) VALUES (...)`
lastOrder, err := f.GetLastFolderOrder(folder.UserId.Int64)
// Generische Funktion nutzen
folder, err = Insert(f.DB, folder, query, folder.UserId, folder.Name, ...)
folder.Order = int32(lastOrder)
return nil
}
Item
:func (i ItemModel) Insert(item *Item) error {
query := `INSERT INTO items (...) VALUES (...)`
lastOrder, err := i.GetLastItemOrder(item.UserId)
// Dieselbe generische Funktion
item, err = Insert(i.DB, item, query, item.UserId, item.ListId, ...)
item.Order = int32(lastOrder)
return nil
}
SetId
-Implementierungentity.ID = id
in Generics. type InsertStrategy[T any] interface {
PrepareQuery() string
PrepareArgs(T) []any
}
Go-Generics sind ein Game-Changer für:
Weiterführende Ressourcen:
„Generics sind wie ein Schweizer Taschenmesser – nicht immer nötig, aber unbezahlbar, wenn man sie braucht.“
©Sergey Emelyanov 2025 | Alle Rechte vorbehalten