Projektstruktur för Go: Praxer och mönster

Strukturera dina Go-projekt för skalbarhet och tydlighet

Sidinnehåll

Att strukturera ett Go-projekt effektivt är avgörande för långsiktig underhållbarhet, team collaboration och skalbarhet. Till skillnad från ramverk som tvingar fram rigida mappstrukturer, omfamnar Go flexibilitet – men med den friheten kommer ansvaret att välja mönster som passar ditt projekts specifika behov.

project tree

Förstå Go:s filosofi för projektstruktur

Go:s minimalistiska designfilosofi sträcker sig även till projektorganisation. Språket dikterar inte en specifik struktur, utan litar på att utvecklare fattar informerade beslut. Detta tillvägagångssätt har lett till att communityn utvecklat flera beprövade mönster, från enkla plana strukturer för små projekt till sofistikerade arkitekturer för enterprise-system.

Den centrala principen är enkelhet först, komplexitet när nödvändigt. Många utvecklare hamnar i fällan att överingeniera sin initiala struktur, vilket skapar djupt nästlade kataloger och förtida abstraktioner. Börja med det du behöver idag och refactora när ditt projekt växer.

När ska jag använda internal/-mappen kontra pkg/?

internal/-mappen har ett specifikt syfte i Go:s design: den innehåller paket som inte kan importeras av externa projekt. Go:s kompilator tillämpar denna begränsning, vilket gör internal/ perfekt för privat applikationslogik, affärsregler och verktyg som inte är avsedda för återanvändning utanför ditt projekt.

pkg/-mappen signalerar å andra sidan att koden är avsedd för extern konsumtion. Placera bara kod här om du bygger ett bibliotek eller återanvändbara komponenter som du vill att andra ska importera. Många projekt behöver inte pkg/ alls – om din kod inte konsumeras externt är det renare att hålla den på roten eller i internal/. När du bygger återanvändbara bibliotek, överväg att utnyttja Go generics för att skapa typsäkra, återanvändbara komponenter.

Standardstruktur för Go-projekt

Det mest vedertagna mönstret är golang-standards/project-layout, även om det är viktigt att notera att detta inte är en officiell standard. Så här ser en typisk struktur 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

Många team håller ett litet delat loggningspaket under internal/ (t.ex. internal/logx) så att binärer i cmd/ kan konfigurera en logger vid start; pkg/logger/ i skissen ovan är lämplig endast när den koden är avsedd att importeras av andra moduler. För en produktionsriktad log/slog-konfiguration (JSON-rader, redigering, kontext- och spårningsfält samt signaler som fungerar bra med övervakningsstackar), se Strukturerad loggning i Go med slog för observabilitet och alertning.

cmd/-mappen

cmd/-mappen innehåller dina applikationens ingångar. Varje undermapp representerar en separat exekverbar binär fil. Till exempel bygger cmd/api/main.go din API-server, medan cmd/worker/main.go kan bygga en bakgrundsjobbprocess.

Bästa praxis: Håll dina main.go-filer minimala – tillräckligt för att koppla ihop beroenden, ladda konfiguration och starta applikationen. All väsentlig logik hör hemma i 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)
    }
}

internal/-mappen

Här bor din privata applikationskod. Go-kompilatorn förhindrar att externa projekt importerar paket inom internal/, vilket gör det idealiskt för:

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

Organisera internal/ efter funktion eller domän, inte efter teknisk lager. 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.

Borde jag börja med en komplex mappstruktur?

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

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

När ditt projekt växer och du identifierar logiska grupperingar, inför kataloger. Du kan lägga till ett db/-paket när databaslogiken blir betydande, eller ett api/-paket när HTTP-hanterarna multipliceras. Låt strukturen uppstå naturligt snarare än att påtvinga den i förväg.

Plan kontra nästlade strukturer: Hitta balansen

Ett av de vanligaste misstagen i Go-projektstruktur är överdriven nästling. Go föredrar platta hierarkier – vanligtvis en eller två nivåer djupt. Djup nästling ökar den kognitiva belastningen och gör importen klumpig.

Vilka är de vanligaste misstagen?

Över-nästlade kataloger: Undvik strukturer som internal/services/user/handlers/http/v1/. Detta skapar onödig navigationskomplexitet. Använd istället internal/user/handler.go.

Generiska paketnamn: Namn som utils, helpers, common eller base är kodlukt (code smells). De förmedlar inte specifik funktionalitet och blir ofta dumpplatser 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 kompileringfel i Go. Detta signalerar oftast dålig separation av intressen. Inför gränssnitt eller extrahera delade typer till ett separat paket.

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

Domän-driven design och hexagonal arkitektur

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

Hur strukturerar jag en mikrotjänst enligt domän-driven design?

Hexagonal arkitektur organiserar kod i koncentriskt lager med beroenden som flötar inåt:

internal/
├── domain/
│   └── user/
│       ├── entity.go        # Domänmodeller 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änst-orchestrering
├── 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        # Ruttkonfiguration och middleware

Domänlagret (domain/): Kernaffärslogik, entiteter, värdeobjekt och domäntjänstgränssnitt. Detta lager har inga beroenden av externa system – inga HTTP- eller databasimporter. Det definierar repository-gränssnitt (portar) som adapter implementerar.

Applikationslagret (application/): Användningsfall som orkestrerar domänobjekt. Varje användningsfall (t.ex. “skapa användare”, “processera betalning”) är en separat fil eller ett separat paket. Detta lager koordinerar domänobjekt men innehåller inga affärsregler i sig.

Adapterlagret (adapter/): Implementerar gränssnitt definierade av inre lager. HTTP-hanterare konverterar begäran till domänobjekt, databasrepository implementerar persistens, meddelandeköer hanterar asynkron kommunikation. Detta lager innehåller all ramverkspecifik och infrastrukturkod.

API-lagret (api/): Rutter, middleware, DTO:n (Data Transfer Objects), API-versionering och OpenAPI-specifikationer.

Denna struktur säkerställer att din kernaffä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 röra domänkoden. Om du antar CQRS inom denna layout, visar Implementering av CQRS i Go hur application/-lagret naturligt kartläggs till separata paket för kommando- och frågehanterare, vilket håller kommandosidan strikt och frågesidan DTO-inriktad.

Praktiska mönster för olika projekttyper

Litet CLI-verktyg

För kommandoradsapplikationer vill du ha en struktur som stöder flera kommandon och subkommandon. Överväg att använda ramverk som Cobra för kommandostruktur 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:n i Go, separerar denna struktur intressen rent: hanterare hanterar HTTP-aspekter, tjänster innehåller affärslogik och repository hanterar datåtkomst. För en omfattande guide som täcker standardbiblioteksmetoder, ramverk, autentisering, testmönster och produktionsklara bästa praxis, se vår kompletta guide till att bygga REST API:n i Go. För strukturerad JSON-loggning, begäran- och spårningskorrelation samt loggformer som stödjer alertning, se Strukturerad loggning i Go med slog för observabilitet och alertning.

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 med flera tjänster

myproject/
├── cmd/
│   ├── api/
│   ├── worker/
│   └── scheduler/
├── internal/
│   ├── shared/        # Delade interna paket
│   ├── api/          # API-specifik kod
│   ├── worker/       # Worker-specifik kod
│   └── scheduler/    # Scheduler-specifik kod
├── pkg/              # Delade bibliotek
├── go.work           # Go-arbetsmilsfil
└── 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 testerna nära implementationen, vilket gör dem lätta att hitta och underhålla. För integrationstester som berör flera paket, skapa en separat test/-mapp på projektets rot. För omfattande vägledning om att skriva effektiva enhetstester, inklusive tabelldrivna tester, mockar, täckningsanalys och bästa praxis, se vår guide till Go enhetstestningens struktur och bästa praxis.

Dokumentation hör hemma i:

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

För REST API:n är att generera och servera OpenAPI-dokumentation med Swagger avgörande för API-upptäckbarhet och utvecklareupplevelse. Vår guide om att lägga till Swagger i ditt Go API täcker integration med populära ramverk och bästa praxis.

Hantera beroenden med Go Modules

Varje Go-projekt bör använda Go Modules för beroendehantering. För en omfattande referens om Go-kommandon och modulantering, kolla vår Go Cheatsheet. Initiera med:

go mod init github.com/yourusername/myproject

Detta skapar go.mod (beroenden och versioner) och go.sum (checksummor för verifiering). Håll dessa filer i versionskontroll för reproducerbara buildar.

Uppdatera beroenden regelbundet:

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

Nyckelpunkter

  1. Börja enkelt, utveckla naturligt: Överingeniera inte din initiala struktur. Lägg till kataloger och paket när komplexitet kräver det.

  2. Föredra plana hierarkier: Begränsa nästling till en eller två nivåer. Go:s plana paketstruktur förbättrar läsbarheten.

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

  4. Separera intressen tydligt: Håll hanterare fokuserade på HTTP, repository på datåtkomst och affärslogik i tjänstepaket.

  5. Utnyttja internal/ för integritet: Använd det för kod som inte bör importeras externt. Mest applikationskod hör hemma här.

  6. Tillämpa arkitekturmönster när nödvändigt: För komplexa system ger hexagonal arkitektur och DDD tydliga gränser och testbarhet.

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

Användbara länkar

Andra relaterade artiklar

Prenumerera

Få nya inlägg om system, infrastruktur och AI-ingenjörskonst.