Section 1: Go services from the ground up
Go refresher for service code
If you already write Go, skim this lesson. If not, these are the features we use throughout the course.
Structs and methods
type Item struct {
ID int64 `json:"id"`
Name string `json:"name"`
OwnerID int64 `json:"-"`
CreatedAt time.Time `json:"created_at"`
}
func (i Item) IsNew(now time.Time) bool {
return now.Sub(i.CreatedAt) < 24*time.Hour
}
Struct tags like json:"name" control how fields are encoded. A tag of json:"-" hides a field from JSON output. Only exported fields, starting with a capital letter, are visible to other packages and to the JSON encoder.
Errors are values
item, err := store.GetItem(ctx, id)
if err != nil {
return fmt.Errorf("get item %d: %w", id, err)
}
Functions return an error as their last value. You check it immediately. Wrapping with %w adds context while keeping the original error inspectable. Callers can test for a specific error with errors.Is:
var ErrNotFound = errors.New("not found")
if errors.Is(err, ErrNotFound) {
// respond with 404
}
Interfaces are small and implicit
type ItemStore interface {
GetItem(ctx context.Context, id int64) (Item, error)
CreateItem(ctx context.Context, in NewItem) (Item, error)
}
A type satisfies an interface automatically if it has the methods. No implements keyword. Define interfaces where they are used, not where they are implemented, and keep them small. Our handlers depend on an ItemStore interface, so tests can pass a fake.
Slices and maps
names := make([]string, 0, len(items))
for _, it := range items {
names = append(names, it.Name)
}
counts := map[string]int{}
counts["overdue"]++
Goroutines and channels, briefly
go doWork() starts a goroutine. Channels pass values between goroutines. In a typical service, the HTTP server creates goroutines for you, and you write goroutines yourself mainly for background jobs. We will use them sparingly and always with a way to stop them.
Modules
go mod init github.com/you/lendr
go get github.com/jackc/pgx/v5
go.mod records the module path and dependencies, and go.sum stores checksums. Both are committed.
Run go vet ./... and gofmt -l . often. They catch common mistakes and formatting differences before review.