Go-projectstructuur: praktijken en patronen

Structureer je Go-projecten voor schaalbaarheid en duidelijkheid

Inhoud

Structureren van een Go-project is fundamenteel voor langdurige onderhoudbaarheid, team samenwerking en schaalbaarheid. In tegenstelling tot frameworks die rigide mapstructuur afdwingen, accepteert Go flexibiliteit—maar met die vrijheid komt ook de verantwoordelijkheid om patronen te kiezen die het specifieke behoeften van je project dienen.

project boom

Begrip van Go’s filosofie over projectstructuur

Go’s minimalistische ontwerpfilosofie reikt uit tot projectorganisatie. De taal stelt geen specifieke structuur voor, maar vertrouwt op ontwikkelaars om geïnformeerd te beslissen. Deze aanpak heeft geleid tot het ontwikkelen van verschillende bewezen patronen, van eenvoudige vlakke lay-outs voor kleine projecten tot geavanceerde architecturen voor enterprise-systemen.

Het belangrijkste principe is eenvoud eerst, complexiteit wanneer nodig. Veel ontwikkelaars vallen in de val van over-engineering hun initiële structuur, waardoor ze diep geneste mappen en vroege abstracties creëren. Begin met wat je vandaag nodig hebt, en refactor als je project groeit.

Wanneer moet ik de interne/ directory gebruiken in plaats van pkg/?

De internal/ directory heeft een specifieke functie in Go’s ontwerp: deze bevat pakketten die niet door externe projecten geïmporteerd mogen worden. Go’s compiler dwingt deze beperking op, waardoor internal/ ideaal is voor privé applicatie logica, bedrijfsregels en hulpmiddelen die niet bedoeld zijn voor hergebruik buiten je project.

De pkg/ directory daarentegen geeft aan dat code bedoeld is voor externe consumptie. Plaats alleen code hier als je een bibliotheek of herbruikbare componenten bouwt die anderen willen importeren. Veel projecten hebben helemaal geen pkg/ nodig—als je code niet extern wordt gebruikt, is het netter om deze in de hoofdmap of in internal/ te houden. Bij het bouwen van herbruikbare bibliotheken, overweeg dan het gebruik van Go generics om typesafe, herbruikbare componenten te maken.

De standaard Go projectstructuur

Het meest erkende patroon is het golang-standards/project-layout, hoewel het belangrijk is op te merken dat dit geen officiële standaard is. Hier is hoe een typische structuur eruitziet:

myproject/
├── cmd/
│   ├── api/
│   │   └── main.go
│   └── worker/
│       └── main.go
├── internal/
│   ├── auth/
│   ├── storage/
│   └── transport/
├── pkg/
│   ├── logger/
│   └── crypto/
├── api/
│   └── openapi.yaml
├── config/
│   └── config.yaml
├── scripts/
│   └── deploy.sh
├── go.mod
├── go.sum
└── README.md

De cmd/ directory

De cmd/ directory bevat de invoerpunten van je toepassing. Elke subdirectory vertegenwoordigt een afzonderlijk uitvoerbaar bestand. Bijvoorbeeld, cmd/api/main.go bouwt je API-server, terwijl cmd/worker/main.go mogelijk een achtergrondtaakverwerker bouwt.

Best practice: Houd je main.go-bestanden zo klein mogelijk—alleen net genoeg om afhankelijkheden te verbinden, configuratie te laden en de toepassing te starten. Alle substantiële logica behoort tot pakketten die main.go importeert.

// cmd/api/main.go
package main

import (
    "log"
    "myproject/internal/server"
    "myproject/internal/config"
)

func main() {
    cfg, err := config.Load()
    if err != nil {
        log.Fatal(err)
    }
    
    srv := server.New(cfg)
    if err := srv.Start(); err != nil {
        log.Fatal(err)
    }
}

De internal/ directory

Hier woont je privé applicatiecode. De Go compiler voorkomt dat externe projecten pakketten binnen internal/ importeren, waardoor het ideaal is voor:

  • Bedrijfslogica en domeinmodellen
  • Toepassingsservices
  • Interne APIs en interfaces
  • Database repositories (voor het kiezen van de juiste ORM, zie onze vergelijking van Go ORMs voor PostgreSQL)
  • Authenticatie en autorisatie logica

Organiseer internal/ op basis van functie of domein, niet op basis van technische laag. In plaats van internal/handlers/, internal/services/, internal/repositories/, gebruik liever internal/user/, internal/order/, internal/payment/, waarbij elk pakket zijn handlers, services en repositories bevat.

Moet ik beginnen met een complexe mapstructuur?

Nee, absoluut niet. Als je een klein hulpmiddel of prototype bouwt, begin dan met:

myproject/
├── main.go
├── go.mod
└── go.sum

Als je project groeit en je logische groepen herkent, introduceer dan mappen. Je kunt bijvoorbeeld een db/ pakket toevoegen als de databaselogica substantieel wordt, of een api/ pakket als de HTTP-handlers vaker worden. Laat de structuur zich natuurlijk ontwikkelen in plaats van het op te leggen vanaf het begin.

Vlakke versus geneste structuren: het vinden van een balans

Een van de meest voorkomende fouten in Go-projectstructuur is overmatige genesting. Go voorkeurt vlakke hierarchieën—meestal één of twee niveaus diep. Diepe genesting verhoogt de cognitieve belasting en maakt imports omslachtig.

Wat zijn de meest voorkomende fouten?

Overgeneste mappen: Vermijd structuren zoals internal/services/user/handlers/http/v1/. Dit creëert onnodige navigatiecomplexiteit. Gebruik liever internal/user/handler.go.

Algemene pakketnamen: Namen zoals utils, helpers, common of base zijn code-geuren. Ze geven geen specifieke functionaliteit aan en worden vaak gebruikt als dumpplaatsen voor ongerelateerde code. Gebruik beschrijvende namen: validator, auth, storage, cache.

Circulaire afhankelijkheden: Als pakket A pakket B importeert en B A importeert, heb je een circulaire afhankelijkheid—een compilatiefout in Go. Dit wijst meestal op slechte scheiding van zorgen. Introduceer interfaces of trek gedeelde typen naar een apart pakket.

Mengen van zorgen: Houd HTTP-handlers gefocust op HTTP-zorgen, databaserepositories op data-toegang en bedrijfslogica in servicepakketten. Plaats bedrijfsregels in handlers maakt testen moeilijk en koppelt je domeinlogica aan HTTP.

Domein-gerichte ontwerp en Hexagonale architectuur

Voor grotere toepassingen, vooral microservices, biedt Domein-gerichte ontwerp (DDD) met Hexagonale architectuur een duidelijke scheiding van zorgen.

Hoe structuur ik een microservice volgens Domein-gerichte ontwerp?

Hexagonale architectuur organiseert code in concentrische lagen met afhankelijkheden die naar binnen vloeien:

internal/
├── domain/
│   └── user/
│       ├── entity.go        # Domeinmodellen en waardeobjecten
│       ├── repository.go    # Repository interface (poort)
│       └── service.go       # Domein services
├── application/
│   └── user/
│       ├── create_user.go   # Use case: gebruiker aanmaken
│       ├── get_user.go      # Use case: gebruiker ophalen
│       └── service.go       # Toepassingsservice coördinatie
├── adapter/
│   ├── http/
│   │   └── user_handler.go  # HTTP adapter (REST endpoints)
│   ├── postgres/
│   │   └── user_repo.go     # Database adapter (implementeert repository poort)
│   └── redis/
│       └── cache.go         # Cache adapter
└── api/
    └── http/
        └── router.go        # Route configuratie en middleware

Domeinlaag (domain/): Kernbedrijfslogica, entiteiten, waardeobjecten en domein service interfaces. Deze laag heeft geen afhankelijkheden op externe systemen—geen HTTP, geen database imports. Het definieert repository interfaces (poorten) die adapters implementeren.

Toepassingslaag (application/): Use cases die domeinobjecten coördineren. Elke use case (bijvoorbeeld “gebruiker aanmaken”, “betaling verwerken”) is een apart bestand of pakket. Deze laag coördineert domeinobjecten maar bevat geen bedrijfsregels zelf.

Adapterlaag (adapter/): Implementeert interfaces gedefinieerd door binnenste lagen. HTTP-handlers converteren aanvragen naar domeinobjecten, databaserepositories implementeren opslag, berichtenwachtrijen hanteren asynchrone communicatie. Deze laag bevat alle frameworkspecifieke en infrastructuurcode.

API-laag (api/): Routes, middleware, DTOs (Data Transfer Objects), API-versiebeheer en OpenAPI-specificaties.

Deze structuur zorgt ervoor dat je kernbedrijfslogica testbaar en onafhankelijk is van frameworks, databases of externe services. Je kunt PostgreSQL vervangen door MongoDB of REST door gRPC zonder de domeincode aan te raken.

Praktische patronen voor verschillende projecttypen

Kleine CLI-tool

Voor command-line applicaties wil je een structuur die meerdere commando’s en subcommando’s ondersteunt. Overweeg het gebruik van frameworks zoals Cobra voor commandostructuur en Viper voor configuratiebeheer. Onze gids over het bouwen van CLI-applicaties in Go met Cobra & Viper behandelt dit patroon in detail.

mytool/
├── main.go
├── command/
│   ├── root.go
│   └── version.go
├── go.mod
└── README.md

REST API-service

Bij het bouwen van REST-apis in Go, schept deze structuur zorgen op een duidelijke manier: handlers hanteren HTTP-zorgen, services bevatten bedrijfslogica en repositories beheren data-toegang. Voor een uitgebreide gids die standaardbibliotheekbenaderingen, frameworks, authenticatie, testpatronen en productiebereidde best practices behandelt, zie onze volledige gids voor het bouwen van REST-apis in Go.

myapi/
├── cmd/
│   └── api/
│       └── main.go
├── internal/
│   ├── config/
│   ├── middleware/
│   ├── user/
│   │   ├── handler.go
│   │   ├── service.go
│   │   └── repository.go
│   └── product/
│       ├── handler.go
│       ├── service.go
│       └── repository.go
├── pkg/
│   └── httputil/
├── go.mod
└── README.md

Monorepo met meerdere services

myproject/
├── cmd/
│   ├── api/
│   ├── worker/
│   └── scheduler/
├── internal/
│   ├── shared/        # Gedeelde interne pakketten
│   ├── api/          # API-specifieke code
│   ├── worker/       # Worker-specifieke code
│   └── scheduler/    # Scheduler-specifieke code
├── pkg/              # Gedeelde bibliotheken
├── go.work           # Go workspace bestand
└── README.md

Testen en documentatie

Plaats testbestanden naast de code die ze testen met de _test.go suffix:

internal/
└── user/
    ├── service.go
    ├── service_test.go
    ├── repository.go
    └── repository_test.go

Deze conventie houdt tests dicht bij de implementatie, waardoor ze gemakkelijk te vinden en onderhouden zijn. Voor integratietests die meerdere pakketten raken, maak een aparte test/ directory aan in de projectroot. Voor uitgebreide richtlijnen over het schrijven van effectieve eenheidstests, inclusief tabelgeleide tests, mocks, coverageanalyse en best practices, zie onze gids voor Go-eenheidsteststructuur en best practices.

Documentatie hoort thuis in:

  • README.md: Projectoverzicht, installatieinstructies, basisgebruik
  • docs/: Gedetailleerde documentatie, architectuurbeslissingen, API-referenties
  • api/: OpenAPI/Swagger-specificaties, protobuf-definities

Voor REST-apis is het genereren en serveren van OpenAPI-documentatie met Swagger essentieel voor API-discoverability en ontwikkelaarservaring. Onze gids over het toevoegen van Swagger aan je Go-api behandelt integratie met populaire frameworks en best practices.

Beheer van afhankelijkheden met Go Modules

Elk Go-project moet Go Modules gebruiken voor het beheer van afhankelijkheden. Voor een uitgebreide referentie over Go-commands en modulebeheer, zie onze Go Cheatsheet. Initialiseer met:

go mod init github.com/yourusername/myproject

Dit maakt go.mod (afhankelijkheden en versies) en go.sum (checksums voor verificatie). Houd deze bestanden in versiebeheer voor reproduceerbare builds.

Bijwerken van afhankelijkheden regelmatig:

go get -u ./...          # Update all dependencies
go mod tidy              # Remove unused dependencies
go mod verify            # Verify checksums

Belangrijkste conclusies

  1. Begin eenvoudig, ontwikkel zichzelf natuurlijk: Over-engineer je initiële structuur niet. Voeg mappen en pakketten toe wanneer complexiteit het vereist.

  2. Voorkeur voor vlakke hierarchieën: Beperk genesting tot één of twee niveaus. Go’s vlakke pakketstructuur verbetert leesbaarheid.

  3. Gebruik beschrijvende pakketnamen: Vermijd algemene namen zoals utils. Noem pakketten naar wat ze doen: auth, storage, validator.

  4. Scheiding van zorgen duidelijk houden: Houd handlers gefocust op HTTP, repositories op data-toegang en bedrijfslogica in servicepakketten.

  5. Gebruik internal/ voor privacy: Gebruik het voor code die niet extern geïmporteerd moet worden. De meeste toepassingscode behoort hier.

  6. Toepassen van architectuurpatronen wanneer nodig: Voor complexe systemen, bieden Hexagonale architectuur en DDD duidelijke grenzen en testbaarheid.

  7. Laat Go je leiden: Volg Go-idioom in plaats van patronen van andere talen. Go heeft zijn eigen filosofie op eenvoud en organisatie.

Andere gerelateerde artikelen