Struktura projektu w Go: praktyki i wzorce
Strukturyzuj swoje projekty Go w celu skalowalności i przejrzystości
Efektywne strukturyzowanie projektu w Go jest fundamentalne dla długofalowej utrzywalności, współpracy w zespole oraz skalowalności. W przeciwieństwie do frameworków, które wymuszają sztywne układy katalogów, Go ceni elastyczność – ale z tą swobodą wiąże się odpowiedzialność za wybór wzorców, które spełniają konkretne potrzeby projektu.

Zrozumienie filozofii Go w zakresie struktury projektu
Minimalistyczna filozofia projektowania w Go rozciąga się również na organizację projektu. Język nie nakłada konkretnego układu, zamiast tego ufając programistom, by podejmowali świadome decyzje. Ten podejście doprowadziło społeczność do opracowania kilku udowodnionych wzorców, od prostych płaskich układów dla małych projektów po zaawansowane architektury dla systemów firmowych.
Kluczny zasada to prostota pierwsza, złożoność tylko wtedy, gdy jest konieczna. Wiele programistów wpada w pułapkę nadmiernego inżynierowania swojej początkowej struktury, tworząc głęboko zagnieżdżone katalogi i wcześniejsze abstrakcje. Zacznij od tego, czego dziś potrzebujesz, a refactoruj, gdy projekt rośnie.
Kiedy należy używać katalogu internal/ zamiast pkg/?
Katalog internal/ ma konkretny cel w projektowaniu Go: zawiera pakiety, które nie mogą być importowane przez projekty zewnętrzne. Kompilator Go wymusza tę ograniczność, czyniąc internal/ idealnym miejscem dla prywatnej logiki aplikacji, reguł biznesowych i narzędzi, które nie są przeznaczone do ponownego wykorzystania poza projektem.
Z kolei katalog pkg/ sygnalizuje, że kod jest przeznaczony do konsumpcji zewnętrzną. Umieszczaj kod tutaj tylko wtedy, gdy tworzysz bibliotekę lub ponownie wykorzystywalne komponenty, które inni będą importować. Wiele projektów nie potrzebuje wcale pkg/ – jeśli kod nie jest konsumowany zewnętrznie, utrzymanie go w katalogu głównym lub w internal/ jest czystsze. Gdy budujesz ponownie wykorzystywalne biblioteki, rozważ wykorzystanie ogólnych typów w Go do tworzenia bezpiecznych typowo, ponownie wykorzystywalnych komponentów.
Standardowy układ projektu w Go
Najbardziej uznawany wzorzec to golang-standards/project-layout, choć warto zauważyć, że nie jest to standard oficjalny. Oto typowy układ:
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
Katalog cmd/
Katalog cmd/ zawiera punkty wejścia aplikacji. Każdy podkatalog reprezentuje osobny wykonywalny binarkę. Na przykład cmd/api/main.go buduje serwer API, a cmd/worker/main.go może budować procesor zadań w tle.
Najlepsze praktyki: Utrzymuj pliki main.go jak najbardziej minimalne – wystarczająco, by połączyć zależności, załadować konfigurację i uruchomić aplikację. Wszystka istotna logika należy do pakietów, które importuje main.go.
// 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)
}
}
Katalog internal/
To miejsce, w którym znajduje się prywatny kod aplikacji. Kompilator Go uniemożliwia projektom zewnętrznym importowanie pakietów w internal/, czyniąc go idealnym dla:
- Logiki biznesowej i modeli domeny
- Usług aplikacji
- Wewnętrznych interfejsów API
- Repozytoriów bazy danych (dla wyboru odpowiedniego ORM, zobacz nasze porównanie ORM dla PostgreSQL w Go)
- Logiki uwierzytelniania i autoryzacji
Organizuj internal/ według funkcji lub domeny, a nie według warstwy technicznej. Zamiast internal/handlers/, internal/services/, internal/repositories/, preferuj internal/user/, internal/order/, internal/payment/, gdzie każdy pakiet zawiera swoje handlery, usługi i repozytoria.
Czy powinienem zacząć od skomplikowanej struktury katalogów?
Zdecydowanie nie. Jeśli tworzysz małą aplikację lub prototyp, zacznij od:
myproject/
├── main.go
├── go.mod
└── go.sum
Gdy projekt rośnie i zauważasz logiczne grupowania, wprowadź katalogi. Możesz dodać pakiet db/, gdy logika baz danych staje się istotna, lub pakiet api/, gdy liczba handlerów HTTP rośnie. Pozwól, by struktura się naturalnie rozwijała, zamiast jej wdrażać od razu.
Układ płaski vs. zagnieżdżony: znalezienie równowagi
Jednym z najczęściej popełnianych błędów w strukturze projektu w Go jest nadmierny zagnieżdżanie. Go preferuje niewielkie hierarchie – zazwyczaj jeden lub dwa poziomy głębokości. Zbyt głębokie zagnieżdżanie zwiększa obciążenie poznawcze i utrudnia importy.
Jakie są najczęstsze błędy?
Zbyt głębokie zagnieżdżanie katalogów: Unikaj struktur jak internal/services/user/handlers/http/v1/. Tworzy to zbędne skomplikowanie nawigacji. Zamiast tego użyj internal/user/handler.go.
Zbyt ogólne nazwy pakietów: Nazwy takie jak utils, helpers, common lub base są smarami kodowymi. Nie przekazują one konkretnych funkcji i często stają się miejscem, w które trafiają niepowiązane fragmenty kodu. Używaj opisowych nazw: validator, auth, storage, cache.
Zależności cyrkularne: Gdy pakiet A importuje pakiet B, a B importuje A, masz zależność cyrkularną – błąd kompilacji w Go. Zwykle oznacza to złe rozdzielanie odpowiedzialności. Wprowadź interfejsy lub wyodrębnij współdzielone typy do osobnego pakietu.
Zmieszanie odpowiedzialności: Utrzymuj handlerzy HTTP skupione na kwestiach HTTP, repozytoria baz danych na dostępie do danych, a logikę biznesową w pakietach usług. Umieszczanie reguł biznesowych w handlerach utrudnia testowanie i łączy logikę domeny z HTTP.
Projektowanie oparte na domenie i architektura heksagonalna
Dla większych aplikacji, szczególnie mikroserwisów, Projektowanie oparte na domenie (DDD) z architekturą heksagonalną zapewnia wyraźne rozdzielanie odpowiedzialności.
Jak strukturyzować mikroserwis zgodnie z DDD?
Architektura heksagonalna organizuje kod w koncentrycznych warstwach, z zależnościami płynącymi wewnętrznie:
internal/
├── domain/
│ └── user/
│ ├── entity.go # Modele domeny i obiekty wartości
│ ├── repository.go # Interfejs repozytorium (port)
│ └── service.go # Usługi domeny
├── application/
│ └── user/
│ ├── create_user.go # Przypadki użycia: tworzenie użytkownika
│ ├── get_user.go # Przypadki użycia: pobieranie użytkownika
│ └── service.go # Orchestracja usług aplikacji
├── adapter/
│ ├── http/
│ │ └── user_handler.go # Adapter HTTP (punkty końcowe REST)
│ ├── postgres/
│ │ └── user_repo.go # Adapter bazy danych (implementuje port repozytorium)
│ └── redis/
│ └── cache.go # Adapter cache
└── api/
└── http/
└── router.go # Konfiguracja tras i middleware
Warstwa domeny (domain/): Główne logiki biznesowe, modele, obiekty wartości i interfejsy usług domeny. Ta warstwa nie ma zależności od systemów zewnętrznych – nie ma importów HTTP ani baz danych. Definiuje interfejsy repozytoriów (porty), które implementują adapterzy.
Warstwa aplikacji (application/): Przypadki użycia, które koordynują obiekty domeny. Każdy przypadek użycia (np. „utwórz użytkownika”, „zrealizuj płatność”) to osobny plik lub pakiet. Ta warstwa koordynuje obiekty domeny, ale nie zawiera samych reguł biznesowych.
Warstwa adapterów (adapter/): Implementuje interfejsy zdefiniowane przez wewnętrzne warstwy. Handlerzy HTTP konwertują żądania na obiekty domeny, repozytoria baz danych implementują trwałość, kolejki wiadomości obsługują komunikację asynchroniczną. Ta warstwa zawiera wszystki kod specyficzny dla frameworków i infrastruktury.
Warstwa API (api/): Trasy, middleware, DTO (Data Transfer Objects), wersjonowanie API i specyfikacje OpenAPI.
Ta struktura zapewnia, że Twoja główna logika biznesowa pozostaje testowalna i niezależna od frameworków, baz danych lub usług zewnętrznych. Możesz zamienić PostgreSQL na MongoDB, a REST na gRPC, bez dotykania kodu domeny.
Praktyczne wzorce dla różnych typów projektów
Mała aplikacja CLI
Dla aplikacji CLI, chcesz strukturę, która obsługuje wiele poleceń i podpoleceń. Rozważ użycie frameworków takich jak Cobra do struktury poleceń i Viper do zarządzania konfiguracją. Nasz przewodnik po budowaniu aplikacji CLI w Go z Cobra i Viper omawia ten wzorzec szczegółowo.
mytool/
├── main.go
├── command/
│ ├── root.go
│ └── version.go
├── go.mod
└── README.md
Usługa API REST
Podczas budowania usług API REST w Go, ta struktura rozdziela odpowiedzialności czysto: handlerzy obsługują kwestie HTTP, usługi zawierają logikę biznesową, a repozytoria zarządzają dostępem do danych. Dla kompletnego przewodnika obejmującego podejścia standardowej biblioteki, frameworki, uwierzytelnianie, wzorce testowe i najlepsze praktyki produkcyjne, zobacz nasz kompletny przewodnik po budowaniu REST API w 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 z wieloma usługami
myproject/
├── cmd/
│ ├── api/
│ ├── worker/
│ └── scheduler/
├── internal/
│ ├── shared/ # Udostępnione wewnętrzne pakiety
│ ├── api/ # Kod specyficzny dla API
│ ├── worker/ # Kod specyficzny dla pracownika
│ └── scheduler/ # Kod specyficzny dla harmonogramu
├── pkg/ # Udostępnione biblioteki
├── go.work # Plik workspace Go
└── README.md
Testowanie i dokumentacja
Umieszczaj pliki testowe obok kodu, który testują, używając sufiksu _test.go:
internal/
└── user/
├── service.go
├── service_test.go
├── repository.go
└── repository_test.go
Ta konwencja utrzymuje testy blisko implementacji, czyniąc je łatwymi do znalezienia i utrzymania. Dla testów integracyjnych, które dotykają wielu pakietów, utwórz osobny katalog test/ w katalogu głównym projektu. Dla kompletnego przewodnika na temat pisania skutecznych testów jednostkowych, w tym testów tabelarycznych, mocków, analizy pokrycia i najlepszych praktyk, zobacz nasz przewodnik po strukturze i najlepszych praktykach testów jednostkowych w Go.
Dokumentacja należy do:
README.md: Omówienie projektu, instrukcje instalacji, podstawowe użyciedocs/: Szczegółowa dokumentacja, decyzje architektoniczne, referencje APIapi/: Specyfikacje OpenAPI/Swagger, definicje protobuf
Dla usług API REST, generowanie i udostępnianie dokumentacji OpenAPI z Swagger jest kluczowe dla odkrywalności API i doświadczenia dewelopera. Nasz przewodnik po dodawaniu Swagger do API w Go obejmuje integrację z popularnymi frameworkami i najlepsze praktyki.
Zarządzanie zależnościami z użyciem Go Modules
Każdy projekt w Go powinien korzystać z Go Modules do zarządzania zależnościami. Dla kompletnego odniesienia do poleceń Go i zarządzania modułami, sprawdź nasz Go Cheatsheet. Inicjalizuj z:
go mod init github.com/yourusername/myproject
To tworzy go.mod (zależności i wersje) i go.sum (sumy kontrolne do weryfikacji). Przechowuj te pliki w kontroli wersji dla reproduowalnych kompilacji.
Regularnie aktualizuj zależności:
go get -u ./... # Aktualizuj wszystkie zależności
go mod tidy # Usuń nieużywane zależności
go mod verify # Weryfikuj sumy kontrolne
Kluczowe wnioski
-
Zacznij od prostego, rozwijaj naturalnie: Nie nadmiernie inżynieruj początkowej struktury. Dodawaj katalogi i pakiety, gdy złożoność wymaga tego.
-
Uwielbiaj płaskie hierarchie: Ogranicz zagnieżdżanie do jednego lub dwóch poziomów. Płaska struktura pakietów w Go poprawia czytelność.
-
Używaj opisowych nazw pakietów: Unikaj ogólnych nazw takich jak
utils. Nazwij pakiety według tego, co robią:auth,storage,validator. -
Jasno rozdzielaj odpowiedzialności: Utrzymuj handlerzy skupione na HTTP, repozytoria na dostępie do danych, a logikę biznesową w pakietach usług.
-
Wykorzystuj
internal/do prywatności: Używaj go do kodu, który nie powinien być importowany zewnętrznie. Większość kodu aplikacji należy tutaj. -
Zastosuj wzorce architekturalne, gdy są potrzebne: Dla złożonych systemów, architektura heksagonalna i DDD zapewnia wyraźne granice i testowalność.
-
Pozwól, by Go kierował Tobą: Dostosuj się do idiomów Go, a nie importuj wzorców z innych języków. Go ma własną filozofię prostoty i organizacji.
Przydatne linki
- Go Cheatsheet
- Kompletny przewodnik po implementowaniu REST API w Go
- Tworzenie aplikacji CLI w Go z Cobra i Viper
- Testowanie jednostkowe w Go: struktura i najlepsze praktyki
- Dodawanie Swagger do API w Go
- Ogólne typy w Go: przypadki użycia i wzorce
- Porównanie ORM dla PostgreSQL w Go: GORM vs Ent vs Bun vs sqlc
Powiązane artykuły
- Standardowy układ projektu w Go – Wskazówki społecznościowe dotyczące struktury projektu
- Dokumentacja Go Modules – Oficjalna dokumentacja Go Modules
- Architektura heksagonalna w Go – Framework architektury heksagonalnej dla systemów firmowych
- Projektowanie oparte na domenie w Go – Gotowa implementacja DDD
- Konwencje struktury projektu w Go – Dodatkowe wzorce i przykłady
- Efektywne Go – Oficjalny przewodnik po najlepszych praktykach Go