Erleben wir Lombok in Golang

Erleben wir Lombok in Golang

24. Oktober 2023

Lombok?

Lombok ist eine Bibliothek in Java, die sich wiederholende Codes wie getter und setter reduziert.

In Go ist das Erstellen von Getter, Setter und Konstruktoren aufgrund der Besonderheiten der Sprache recht mühsam.

In Sprachen wie Java gibt es Bibliotheken, die solche Arbeiten automatisieren.

Angenommen, wir erstellen einen Konstruktor für die folgende Struktur.

type User struct {
    Name string
    Age int
}

Die meisten würden wahrscheinlich einen Konstruktor wie folgt erstellen.

func NewUser(name string, age int) User {
    return User{
        Name: name,
        Age: age,
    }
}

In Java wird der Konstruktor meist so erstellt:

public class User {
    private String name;
    private int age;
    
    public User(String name, int age){
        this.name = name;
        this.age = age;
    } 
}

Eine Bibliothek, die diese Arbeiten automatisiert, ist Lombok. Mit Lombok kann man wie folgt arbeiten.

import lombok.AllArgsConstructor;

@AllArgsConstructor
public class User {
    private String name;
    private int age;
}

Es ist eine Bibliothek, die es Entwicklern ermöglicht, repetitive Aufgaben wie das Erstellen von Konstruktoren zu vermeiden und menschliche Fehler zu reduzieren.

Gombok

Aufgrund der vielen Boilerplate-Codes in Go ist es schwierig, sich auf das Schreiben des tatsächlichen Geschäftscodes zu konzentrieren, wenn man solche Codes weiterhin schreibt. (vielleicht bin ich einfach nur faul..) Ich habe darüber nachgedacht, wie ich dies automatisieren kann, und habe eine Code-Generator-Bibliothek namens gombok entwickelt, indem ich lombok und go kombinierte, damit wir es im Unternehmen gut nutzen konnten.

Installation

Die Installation kann einfach mit dem Befehl install durchgeführt werden.

 $ go install github.com/YangTaeyoung/gombok@v1.1.0

Nutzung

Man schreibt einfach die Annotations, die man in Lombok verwendet hat, zusammen mit Kommentaren in die Struktur, die man verwenden möchte.

// @AllArgsConstructor
// @Builder
// @Getter
// @Setter
type User struct {
    Name string
    Age int
}

Dann führt man es wie folgt aus. Man braucht keine besonderen Optionen anzugeben, und beim Ausführen des Befehls wird eine Datei mit dem Namen {dateiname}_gombok.go erzeugt.

$ gombok

> main.go
> 2023/10/24 23:25:39 Found @AllArgsConstructor in User
> 2023/10/24 23:25:39 Found @Builder in User
> 2023/10/24 23:25:39 Found @Getter in User
> 2023/10/24 23:25:39 Found @Setter in User

Wenn man die entsprechende Datei öffnet, kann man sehen, dass der Konstruktor, der Builder, der Getter und der Setter wie folgt erstellt wurden.

// Code generated by gombok. DO NOT EDIT.
package main

// NewUserWithAllArgs
func NewUserWithAllArgs(name string, age int) User {
	return User{
		Name: name,
		Age:  age,
	}
}

// UserBuilder
// ein Builder für User
type UserBuilder struct {
	target *User
}

// WithName
// setzt das Name-Feld des Ziels User
func (ub UserBuilder) WithName(name string) UserBuilder {
	ub.target.Name = name

	return ub
}

// WithAge
// setzt das Age-Feld des Ziels User
func (ub UserBuilder) WithAge(age int) UserBuilder {
	ub.target.Age = age

	return ub
}

// Build
// konstruiert ein User aus dem Builder
func (ub UserBuilder) Build() User {
	return *ub.target
}

// NewUserBuilder
// erstellt eine neue Builder-Instanz für User
func NewUserBuilder() UserBuilder {
	return UserBuilder{target: &User{}}
}

// GetName
func (u *User) GetName() string {
	return u.Name
}

// GetAge
func (u *User) GetAge() int {
	return u.Age
}

// SetName
func (u *User) SetName(name string) {
	u.Name = name
}

// SetAge
func (u *User) SetAge(age int) {
	u.Age = age
}

Man kann einfach die generierten Receiver-Funktionen an den gewünschten Stellen verwenden.

// @AllArgsConstructor
// @Builder
// @Getter
// @Setter
type User struct {
	Name string
	Age  int
}

func main() {
	user := NewUserBuilder().WithAge(10).WithName("John").Build()

	fmt.Println(user.GetName(), user.GetAge())

	user.SetName("Jane")
	user.SetAge(20)

	fmt.Println(user.GetName(), user.GetAge())

	user2 := NewUserWithAllArgs("giraffe", 27)

	fmt.Println(user2.GetName(), user2.GetAge())
}

Ergebnis

John 10
Jane 20
giraffe 27