Goprojektstruktur: Praxis och mönster

Strukturera dina Go-projekt för skalbarhet och tydlighet

Sidinnehåll

Att strukturera ett Go-projekt är avgörande för långsiktig underhållbarhet, teamarbete och skalbarhet. Till skillnad från ramverk som tvingar en strikt kataloglayout, accepterar Go flexibilitet – men med den friheten kommer ansiktheten att välja mönster som passar ditt projekts specifika behov.

projektträd

Förstå Goss filosofi kring projektstruktur

Gos minimalistiska designfilosofi utsträcker sig till projektorganisation. Språket kräver inte en specifik struktur, istället förtroende för utvecklare att göra informerade beslut. Denna metod har lett till att gemenskapen utvecklat flera beprövade mönster, från enkla plana layouter för små projekt till avancerade arkitekturer för företagsystem.

Den viktigaste principen är enkelskap först, komplexitet när det är nödvändigt. Många utvecklare hamnar i fällan att överdriva sin initiala struktur, skapa djupt nystade kataloger och för tidig abstraktion. Börja med vad du behöver idag, och refaktorera när ditt projekt växer.

När ska jag använda katalogen internal/ i stället för pkg/?

Katalogen internal/ har en specifik funktion i Goss design: den innehåller paket som inte kan importeras av externa projekt. Goss kompilator tvingar denna begränsning, vilket gör internal/ perfekt för privat applikationslogik, affärsregler och verktyg som inte ska användas utanför ditt projekt.

Katalogen pkg/ signalerar att koden är avsedd för extern användning. Placera endast kod här om du bygger en bibliotek eller återanvändbara komponenter som andra ska importera. Många projekt behöver inte pkg/ alls – om din kod inte används extern, är det renare att behålla den i roten eller i internal/. När du bygger återanvändbara bibliotek, överväg att använda Go generics för att skapa typsäkra, återanvändbara komponenter.

Den standard Go projektlayout

Det mest kända mönstret är golang-standards/project-layout, även om det är viktigt att notera att detta inte är ett officiellt standard. Här är hur en typisk struktur ser ut:

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

Katalogen cmd/

Katalogen cmd/ innehåller dina applikationsstartpunkter. Varje underkatalog representerar en separat exekverbar fil. Till exempel bygger cmd/api/main.go din API-server, medan cmd/worker/main.go kan bygga en bakgrundsjobbhanterare.

Bästa praxis: Håll dina main.go-filer minimala – bara tillräckligt för att ansluta beroenden, ladda konfiguration och starta applikationen. All betydande logik tillhör paket som main.go importerar.

// 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)
    }
}

Katalogen internal/

Det är här din privata applikationskod finns. Goss kompilator förhindrar att något extern projekt importerar paket inom internal/, vilket gör det perfekt för:

  • Affärslogik och domännodeller
  • Applikationstjänster
  • Interna API:er och gränssnitt
  • Databasrepository (för att välja rätt ORM, se vår jämförelse av Go ORMs för PostgreSQL)
  • Autentisering och auktoriseringslogik

Organisera internal/ efter funktion eller domän, inte efter teknisk nivå. Istället för internal/handlers/, internal/services/, internal/repositories/, föredra internal/user/, internal/order/, internal/payment/ där varje paket innehåller sina hanterare, tjänster och repository.

Ska jag börja med en komplex katalogstruktur?

Absolut inte. Om du bygger ett litet verktyg eller prototyp, börja med:

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

När ditt projekt växer och du identifierar logiska grupperingar, introducera kataloger. Du kan till exempel lägga till ett db/-paket när databaslogik blir betydande, eller ett api/-paket när HTTP-hanterare multipliceras. Låt strukturen växa naturligt istället för att tvinga den fram i förväg.

Platta mot nystade strukturer: Hitta balansen

En av de vanligaste felet i Go-projektstruktur är överdriven nystning. Go föredrar skala hierarkier – vanligtvis en eller två nivåer djup. Djup nystning ökar kognitiv last och gör importer tunga.

Vad är de vanligaste felet?

Överdriven katalognystning: Undvik strukturer som internal/services/user/handlers/http/v1/. Det skapar onödig navigeringskomplexitet. Använd istället internal/user/handler.go.

Allmänna paketnamn: Namn som utils, helpers, common eller base är kodlukt. De visar inte specifik funktion och ofta blir dumpplats för orelaterad kod. Använd beskrivande namn: validator, auth, storage, cache.

Cirkulära beroenden: När paket A importerar paket B, och B importerar A, har du ett cirkulärt beroende – ett kompileringsfel i Go. Det signalerar ofta dålig separation av ansvar. Introducera gränssnitt eller extrahera delade typer i ett separat paket.

Blandade ansvar: Håll HTTP-hanterare fokuserade på HTTP-ansvar, databasrepository på dataåtkomst och affärslogik i tjänstepaket. Att placera affärsregler i hanterare gör testning svårare och kopplar din domännlogik till HTTP.

Domain-Driven Design och Hexagonal Arkitektur

För större applikationer, särskilt mikrotjänster, Domain-Driven Design (DDD) med Hexagonal Arkitektur ger tydlig separation av ansvar.

Hur strukturera en mikrotjänst enligt Domain-Driven Design?

Hexagonal Arkitektur organiserar koden i koncentriska lager med beroenden som flyter inåt:

internal/
├── domain/
│   └── user/
│       ├── entity.go        # Domännodeller och värdeobjekt
│       ├── repository.go    # Repository gränssnitt (port)
│       └── service.go       # Domäntjänster
├── application/
│   └── user/
│       ├── create_user.go   # Användningsfall: skapa användare
│       ├── get_user.go      # Användningsfall: hämta användare
│       └── service.go       # Applikationstjänstkoordination
├── adapter/
│   ├── http/
│   │   └── user_handler.go  # HTTP-adapter (REST-slutpunkter)
│   ├── postgres/
│   │   └── user_repo.go     # Databasadapter (implementerar repository-port)
│   └── redis/
│       └── cache.go         # Cache-adapter
└── api/
    └── http/
        └── router.go        # Routkonfiguration och mellanprogram

Domänlager (domain/): Kärnaffärslogik, entiteter, värdeobjekt och domäntjänstgränssnitt. Detta lager har inga beroenden på externa system – ingen HTTP, inga databasimporter. Det definierar repositorygränssnitt (portar) som adapter implementerar.

Applikationslager (application/): Användningsfall som koordinerar domännodeller. Varje användningsfall (t.ex. “skapa användare”, “bearbeta betalning”) är en separat fil eller paket. Detta lager koordinerar domännodeller men innehåller inga affärsregler självt.

Adapterlager (adapter/): Implementerar gränssnitt som definierats av inre lager. HTTP-hanterare konverterar förfrågningar till domännodeller, databasrepository implementerar persistens, meddelandeköer hanterar asynkron kommunikation. Detta lager innehåller all ramverks- och infrastrukturkod.

API-lager (api/): Routar, mellanprogram, DTO:er (Data Transfer Objects), API-versionering och OpenAPI-specifikationer.

Denna struktur säkerställer att din kärnaffärslogik förblir testbar och oberoende av ramverk, databaser eller externa tjänster. Du kan byta ut PostgreSQL mot MongoDB, eller REST mot gRPC utan att behöva ändra domännodellkod.

Praktiska mönster för olika projekttyper

Liten CLI-verktyg

För kommandoradsapplikationer vill du ha en struktur som stöder flera kommandon och underkommandon. Overväg att använda ramverk som Cobra för kommandouppbyggnad och Viper för konfigurationshantering. Vår guide om att bygga CLI-applikationer i Go med Cobra & Viper täcker detta mönster i detalj.

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

REST API-tjänst

När du bygger REST API:er i Go, skiljer denna struktur ansvar tydligt: hanterare hanterar HTTP-ansvar, tjänster innehåller affärslogik och repository hanterar dataåtkomst. För en omfattande guide som täcker standardbiblioteksmetoder, ramverk, autentisering, testmönster och produktionsklara bästa praxis, se vår komplett guide för att bygga REST API:er i Go.

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

Monorepo med flera tjänster

myproject/
├── cmd/
│   ├── api/
│   ├── worker/
│   └── scheduler/
├── internal/
│   ├── shared/        # Delade interna paket
│   ├── api/          # API-specifika kod
│   ├── worker/       # Worker-specifika kod
│   └── scheduler/    # Scheduler-specifika kod
├── pkg/              # Delade bibliotek
├── go.work           # Go workspace-fil
└── README.md

Testning och dokumentation

Placera testfiler bredvid koden de testar med suffixet _test.go:

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

Denna konvention håller testen nära implementationen, vilket gör dem lätt att hitta och underhålla. För integrationstester som rör flera paket, skapa en separat test/-katalog vid projekts roten. För omfattande vägledning om hur man skriver effektiva enhetstester, inklusive tabellbaserade tester, mockar, täckningsanalys och bästa praxis, se vår guide till Go-enhets-teststruktur och bästa praxis.

Dokumentationen ska finnas i:

  • README.md: Projektöversikt, installationsinstruktioner, grundläggande användning
  • docs/: Detaljerad dokumentation, arkitekturbeslut, API-referenser
  • api/: OpenAPI/Swagger-specifikationer, protobuf-definitioner

För REST API:er är det avgörande att generera och serva OpenAPI-dokumentation med Swagger för API-upptäckbarhet och utvecklarens upplevelse. Vår guide om att lägga till Swagger i din Go API täcker integration med populära ramverk och bästa praxis.

Hantera beroenden med Go-moduler

Varje Go-projekt bör använda Go-moduler för beroendehantering. För en omfattande referens om Go-kommandon och modulhantering, se vår Go-kortguide. Initiera med:

go mod init github.com/yourusername/myproject

Detta skapar go.mod (beroenden och versioner) och go.sum (kontrollsummor för verifikation). Håll dessa filer i versionskontroll för återproducerbara bygg.

Uppdatera beroenden regelbundet:

go get -u ./...          # Uppdatera alla beroenden
go mod tidy              # Ta bort oanvända beroenden
go mod verify            # Verifiera kontrollsummor

Viktiga slutsatser

  1. Börja enkelt, utveckla naturligt: Undvik att överdriva din initiala struktur. Lägg till kataloger och paket när komplexiteten kräver det.

  2. Föredra platta hierarkier: Begräns nystning till en eller två nivåer. Goss platta paketstruktur förbättrar läsbarhet.

  3. Använd beskrivande paketnamn: Undvik generiska namn som utils. Namnge paket efter vad de gör: auth, storage, validator.

  4. Skilj ansvar tydligt: Håll hanterare fokuserade på HTTP, repository på dataåtkomst och affärslogik i tjänstepaket.

  5. Utnyttja internal/ för integritet: Använd det för kod som inte ska importeras extern. Många applikationskoder tillhör här.

  6. Använd arkitekturmönster när det behövs: För komplexa system, ger Hexagonal Arkitektur och DDD tydliga gränser och testbarhet.

  7. Låt Go guida dig: Följ Go-idiom istället för att importera mönster från andra språk. Go har sin egen filosofi kring enkelhet och organisation.

Några användbara länkar

Andra relaterade artiklar