Struktura projektu w Go: praktyki i wzorce
Strukturyzuj projekty w Go w celu zapewnienia skalowalności i przejrzystości
Eфекtywne strukturzenie projektu w Go jest fundamentem długoterminowej łatwości utrzymania, współpracy w zespole oraz skalowalności. W przeciwieństwie do frameworków, które narzucają sztywne układy katalogów, Go czerpie z elastyczności – ale z tą wolnością wiąże się odpowiedzialność za wybór wzorców, które służą specyficznym potrzebom Twojego projektu.

Zrozumienie filozofii Go dotyczącej struktury projektu
Minimalistyczna filozofia projektowa Go rozciąga się na organizację projektu. Język nie narzuca konkretnej struktury, ufając zamiast temu deweloperom w podejmowaniu świadomych decyzji. Podejście to doprowadziło społeczność do opracowania kilku sprawdzonych wzorców, od prostych, płaskich układów dla małych projektów po zaawansowane architektury dla systemów enterprise.
Kluczową zasadą jest prostość na pierwszym miejscu, złożoność tylko gdy jest to konieczne. Wielu deweloperów wpada w pułapkę nadmiernego inżynierowania początkowej struktury, tworząc głęboko zagnieżdżone katalogi i przedwczesne abstrakcje. Zacznij od tego, czego potrzebujesz dzisiaj, i refaktoryzuj, gdy Twój projekt będzie się rozwijać.
Kiedy powinnem używać katalogu internal/ zamiast pkg/?
Katalog internal/ służy specyficznemu celowi w projektowaniu Go: zawiera pakiety, których nie można importować w zewnętrznych projektach. Kompilator Go egzekwuje to ograniczenie, czyniąc internal/ idealnym miejscem na prywatną logikę aplikacji, reguły biznesowe i narzędzia, które nie są przeznaczone do ponownego wykorzystania poza Twoim projektem.
Z kolei katalog pkg/ sygnalizuje, że kod jest przeznaczony do spożycia przez osoby zewnętrzne. Umieszczaj kod tutaj tylko wtedy, gdy budujesz bibliotekę lub komponenty do ponownego użycia, które chcesz, aby inni importowali. Wiele projektów nie potrzebuje pkg/ wcale – jeśli Twój kod nie jest konsumowany zewnętrznie, utrzymywanie go w katalogu głównym lub w internal/ jest czystsze. Przy budowaniu wielokrotnego wykorzystania bibliotek, rozważ wykorzystanie typów generycznych w Go, aby tworzyć typowo bezpieczne, wielokrotnie wykorzystywalne komponenty.
Standardowy układ projektu Go
Najbardziej rozpoznawalnym wzorcem jest golang-standards/project-layout, choć należy pamiętać, że nie jest to oficjalny standard. Oto jak wygląda typowa struktura:
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
Wiele zespołów utrzymuje mały, wspólny pakiet logowania pod internal/ (na przykład internal/logx), więc binaria w cmd/ podłącza jeden logger przy starcie; pkg/logger/ w powyższym szkicu jest odpowiedni tylko wtedy, gdy ten kod ma być importowany przez inne moduły. Dla produkcyjnie nastawionej konfiguracji log/slog (linijki JSON, redakcja, pola kontekstu i śledzenia oraz sygnały dobrze współpracujące ze stackami monitoringu), zobacz Strukturalne logowanie w Go z slog dla obserwowalności i alertów.
Katalog cmd/
Katalog cmd/ zawiera punkty wejścia Twojej aplikacji. Każdy podkatalog reprezentuje osobny plik wykonywalny. Na przykład cmd/api/main.go buduje serwer API, podczas gdy cmd/worker/main.go może budować procesor zadań w tle.
Najlepsza praktyka: Utrzymuj pliki main.go w minimalnej formie – wystarczająco, aby podłączyć zależności, załadować konfigurację i uruchomić aplikację. Cała 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/
Tutaj znajduje się Twój prywatny kod aplikacji. Kompilator Go uniemożliwia importowanie pakietów z internal/ przez zewnętrzne projekty, co czyni go idealnym dla:
- Logiki biznesowej i modeli domenowych
- Usług aplikacji
- Wewnętrznych API i interfejsów
- Repozytoriów baz danych (dla wyboru odpowiedniego ORM zobacz nasz porównanie ORMów Go dla PostgreSQL)
- Logiki uwierzytelniania i autoryzacji
Organizuj internal/ według funkcji lub domeny, a nie warstw technicznych. Zamiast internal/handlers/, internal/services/, internal/repositories/, preferuj internal/user/, internal/order/, internal/payment/, gdzie każdy pakiet zawiera swoje obsłużniki, usługi i repozytoria.
Czy powiniem zacząć od złożonej struktury katalogów?
Absolutnie nie. Jeśli budujesz małe narzędzie lub prototyp, zacznij od:
myproject/
├── main.go
├── go.mod
└── go.sum
Gdy Twój projekt rośnie i identyfikujesz logiczne grupowania, wprowadź katalogi. Możesz dodać pakiet db/, gdy logika bazy danych stanie się istotna, lub pakiet api/, gdy obsłużniki HTTP się pomnożą. Pozwól strukturze wyłaniać się naturalnie, zamiast narzucać ją od razu.
Struktury płaskie vs. zagnieżdżone: szukanie równowagi
Jednym z najczęstszych błędów w strukturze projektu Go jest nadmierne zagnieżdżanie. Go faworyzuje płytkie hierarchie – zazwyczaj jeden lub dwa poziomy głębokości. Głębokie zagnieżdżanie zwiększa obciążenie poznawcze i sprawia, że importy są uciążliwe.
Jakie są najczęstsze błędy?
Nadmierne zagnieżdżanie katalogów: Unikaj struktur takich jak internal/services/user/handlers/http/v1/. Tworzy to niepotrzebnie złożoność nawigacji. Zamiast tego użyj internal/user/handler.go.
Ogólnikowe nazwy pakietów: Nazwy takie jak utils, helpers, common lub base są zapachami kodu (code smells). Nie przekazują one konkretnej funkcjonalności i często stają się miejscami składowania niezwiązanej ze sobą kodu. Używaj opisowych nazw: validator, auth, storage, cache.
Zależności cykliczne: Gdy pakiet A importuje pakiet B, a B importuje A, masz do czynienia z zależnością cykliczną – błędem kompilacji w Go. Zazwyczaj sygnalizuje to słabe rozdzielenie odpowiedzialności. Wprowadź interfejsy lub wyodrębnij wspólne typy do osobnego pakietu.
Mieszanie odpowiedzialności: Utrzymuj obsłużniki HTTP skoncentrowane na kwestiach HTTP, repozytoria na dostępie do danych, a logikę biznesową w pakietach usług. Umieszczanie reguł biznesowych w obsłużnikach utrudnia testowanie i wiąże logikę domenową 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 jasne rozdzielenie odpowiedzialności.
Jak zbudować mikroserwis zgodnie z Projektowaniem Opartym na Domenie?
Architektura Heksagonalna organizuje kod w koncentryczne warstwy, z zależnościami płynącymi do środka:
internal/
├── domain/
│ └── user/
│ ├── entity.go # Modele domenowe i obiekty wartościowe
│ ├── repository.go # Interfejs repozytorium (port)
│ └── service.go # Usługi domenowe
├── application/
│ └── user/
│ ├── create_user.go # Przypadek użycia: utwórz użytkownika
│ ├── get_user.go # Przypadek użycia: pobierz użytkownika
│ └── service.go # Orkiestracja 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 pamięci podręcznej
└── api/
└── http/
└── router.go # Konfiguracja tras i middleware
Warstwa domenowa (domain/): Podstawowa logika biznesowa, encje, obiekty wartościowe i interfejsy usług domenowych. Ta warstwa nie ma zależności od systemów zewnętrznych – brak importów HTTP, brak importów bazy danych. Definiuje interfejsy repozytoriów (porty), które implementują adaptery.
Warstwa aplikacji (application/): Przypadki użycia, które orkiestrują obiekty domenowe. Każdy przypadek użycia (np. „utwórz użytkownika”, „przetwórz płatność”) jest osobnym plikiem lub pakietem. Ta warstwa koordynuje obiekty domenowe, ale sama nie zawiera reguł biznesowych.
Warstwa adapterów (adapter/): Implementuje interfejsy zdefiniowane przez wewnętrzne warstwy. Obsłużniki HTTP konwertują żądania na obiekty domenowe, repozytoria baz danych implementują trwałość, kolejki wiadomości obsługują komunikację asynchroniczną. Ta warstwa zawiera cały kod specyficzny dla frameworków i infrastruktury.
Warstwa API (api/): Trasy, middleware, DTO (Obiekty Przenoszenia Danych), wersjonowanie API i specyfikacje OpenAPI.
Ta struktura zapewnia, że Twoja podstawowa logika biznesowa pozostaje testowalna i niezależna od frameworków, baz danych lub usług zewnętrznych. Możesz zamienić PostgreSQL na MongoDB lub REST na gRPC, nie dotykając kodu domenowego. Jeśli wdrożysz CQRS w tej strukturze, Wdrażanie CQRS w Go pokazuje, jak warstwa application/ naturalnie mapuje się na osobne pakiety obsługujące polecenia i zapytania, utrzymując stronę poleceń rygorystyczną, a stronę zapytań nastawioną na DTO.
Praktyczne wzorce dla różnych typów projektów
Małe narzędzie CLI
Dla aplikacji linii poleceń będziesz potrzebować struktury wspierającej wiele komend i podkomend. Rozważ użycie frameworków takich jak Cobra dla struktury komend i Viper do zarządzania konfiguracją. Nasz przewodnik na temat budowania aplikacji CLI w Go z Cobra & Viper omawia ten wzorzec szczegółowo.
mytool/
├── main.go
├── command/
│ ├── root.go
│ └── version.go
├── go.mod
└── README.md
Usługa REST API
Przy budowaniu interfejsów API REST w Go ta struktura czyści rozdziela odpowiedzialności: obsłużniki zajmują się kwestiami HTTP, usługi zawierają logikę biznesową, a repozytoria zarządzają dostępem do danych. Dla kompleksowego przewodnika obejmującego podejścia biblioteki standardowej, frameworki, uwierzytelnianie, wzorce testowania i najlepsze praktyki gotowe na produkcję, zobacz nasz kompletny przewodnik do budowania interfejsów API REST w Go. Dla strukturalnego logowania JSON, korelacji żądań i śledzenia oraz kształtów logów wspierających alerty, zobacz Strukturalne logowanie w Go z slog dla obserwowalności i alertów.
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/ # Wspólne pakiety wewnętrzne
│ ├── api/ # Kod specyficzny dla API
│ ├── worker/ # Kod specyficzny dla Worker
│ └── scheduler/ # Kod specyficzny dla Scheduler
├── pkg/ # Wspólne biblioteki
├── go.work # Plik przestrzeni roboczej 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, co ułatwia ich znajdowanie i utrzymanie. Dla testów integracyjnych dotykających wielu pakietów, utwórz osobny katalog test/ w katalogu głównym projektu. Dla kompleksowych wskazówek dotyczących pisania skutecznych testów jednostkowych, w tym testów napędzanych tabelami, mocków, analizy pokrycia i najlepszych praktyk, zobacz nasz przewodnik do struktury testów jednostkowych w Go i najlepszych praktyk.
Dokumentacja należy do:
README.md: Przegląd projektu, instrukcje konfiguracji, podstawowe użyciedocs/: Szczegółowa dokumentacja, decyzje architektoniczne, referencje APIapi/: Specyfikacje OpenAPI/Swagger, definicje protobuf
Dla interfejsów API REST generowanie i udostępnianie dokumentacji OpenAPI ze Swagger jest kluczowe dla wykrywalności API i doświadczenia deweloperskiego. Nasz przewodnik na temat dodawania Swaggera do API w Go omawia integrację z popularnymi frameworkami i najlepsze praktyki.
Zarządzanie zależnościami z modułami Go
Każdy projekt Go powinien używać Modułów Go do zarządzania zależnościami. Dla kompleksowego odniesienia do poleceń Go i zarządzania modułami, sprawdź nasz Cheat sheet Go). Zainicjuj za pomocą:
go mod init github.com/yourusername/myproject
Tworzy to go.mod (zależności i wersje) oraz go.sum (sumy kontrolne do weryfikacji). Utrzymuj te pliki w systemie kontroli wersji dla odtwarzalnych 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 # Zweryfikuj sumy kontrolne
Kluczowe wnioski
-
Zacznij od prostej struktury, ewoluuj naturalnie: Nie nadmiernie inżynieruj początkowej struktury. Dodawaj katalogi i pakiety, gdy złożoność tego wymaga.
-
Faworyzuj płaskie hierarchie: Ogranicz zagnieżdżanie do jednego lub dwóch poziomów. Płaka struktura pakietów Go poprawia czytelność.
-
Używaj opisowych nazw pakietów: Unikaj ogólnikowych nazw takich jak
utils. Nazwij pakiety według tego, co robią:auth,storage,validator. -
Jasno rozdzielaj odpowiedzialności: Utrzymuj obsłużniki skoncentrowane na HTTP, repozytoria na dostępie do danych, a logikę biznesową w pakietach usług.
-
Wykorzystuj
internal/do prywatności: Używaj go dla kodu, który nie powinien być importowany zewnętrznie. Większość kodu aplikacji należy tutaj. -
Stosuj wzorce architektoniczne, gdy jest to konieczne: Dla złożonych systemów Architektura Heksagonalna i DDD zapewniają jasne granice i testowalność.
-
Niech Go Cię prowadzi: Postępuj zgodnie z idiomami Go, zamiast importować wzorce z innych języków. Go ma swoją własną filozofię prostoty i organizacji.
Przydatne linki
- Cheat sheet Go
- Budowanie interfejsów API REST w Go: Kompletny przewodnik
- Budowanie aplikacji CLI w Go z Cobra & Viper
- Testy jednostkowe w Go: Struktura i najlepsze praktyki
- Strukturalne logowanie w Go z slog dla obserwowalności i alertów
- Dodawanie Swaggera do API w Go
- Typy generyczne w Go: Przypadki użycia i wzorce
- Porównanie ORMów Go dla PostgreSQL: GORM vs Ent vs Bun vs sqlc
Inne powiązane artykuły
- Standardowy układ projektu Go - Wytyczne struktury projektu tworzone przez społeczność
- Referencja Modułów Go - Oficjalna dokumentacja modułów Go
- Architektura heksagonalna w Go - Framework architektury heksagonalnej klasy enterprise
- Projektowanie oparte na domenie w Go - Gotowa na produkcję implementacja DDD
- Konwencje struktury projektu Go - Dodatkowe wzorce i przykłady
- Effective Go - Oficjalny przewodnik po najlepszych praktykach Go
- Centrum architektury aplikacji — projekt API, struktura kodu i wzorce integracji