Menu

Golang Proje Yapısı: cmd, internal ve Paket Düzeni

Bir Go projesini düzenlemek: düz başlayın, bir neden olduğunda paketlere bölün, birden fazla binary için cmd/, başka kimsenin import etmemesi gereken kod için internal/ kullanın, paketleri iyi adlandırın ve testleri kodun yanında tutun.

Bu sayfada çalıştırılabilir editörler var - düzenle, çalıştır ve sonucu anında gör.

Tek bir dosyayla başlayın

Bir Go programı tamamen tek bir main.go içinde yaşayabilir ve küçük araçlar öyle yaşamalıdır. Bu eksiksiz program girdiyi ayrıştırır, işini yapar ve bir rapor yazdırır; hepsi tek bir pakette:

Büyüdüğünde onu aynı dizinde ve aynı pakette daha fazla dosyaya bölün (parse.go, report.go, main.go). Bir paketteki dosyalar her tanımlayıcıyı paylaşır, bu yüzden hiçbir şeyin dışa açılması ya da import edilmesi gerekmez. Birkaç bin satırlık bir projenin ihtiyaç duyduğu yapı çoğu zaman yalnızca budur.

Çok dosyalı bir package main'in bir paket olarak çalıştırılması gerektiğini unutmayın: go run main.go değil, go run .. Aksi halde dosyalar tek başına derlenir ve diğer dosyalarda tanımlanan adlar için undefined hataları alırsınız.

Zorunlu bir düzen yok

Go, "dizin başına bir paket" dışında hiçbir dizin yapısı gerektirmez. Resmi rehber go.dev'deki Organizing a Go module sayfasıdır ve kurallar değil, birkaç biçim anlatır.

GitHub deposu golang-standards/project-layout çok kopyalanır ve çoğu zaman bir standart sanılır. Büyük projelerden gelen geleneklerin bir topluluk derlemesidir ve o dönem Go'nun teknik lideri olan Russ Cox orada bunun bir Go standardı olmadığını belirten bir issue açmıştır. Dizinlerinin çoğu (pkg/, api/, build/, deployments/) yalnızca büyük kod tabanlarında anlamlıdır. Tüm iskeleti yeni bir projeye kopyalamak hiçbir fayda sağlamadan boş klasörler ve derin import yolları yaratır.

Aracın zorladığı kurallar kısadır:

  • Bir dizin bir pakettir. İçindeki tüm .go dosyaları (_test sonekini kullanan _test.go dosyaları hariç) bir paket adını paylaşır.
  • Import yolu modül yolu artı dizin yoludur.
  • internal adlı bir dizin, içindekini kimin import edebileceğini kısıtlar.
  • testdata adlı dizinler ve . ya da _ ile başlayanlar go aracı tarafından yok sayılır.

Yaygın biçimler

Tek bir komut

todo/
├── go.mod        module github.com/you/todo
├── main.go
├── parse.go
├── report.go
└── parse_test.go

Düz, her şey package main içinde. go install github.com/you/todo@latest çalışır ve binary'nin adı todo olur.

Bir kütüphane

slug/
├── go.mod        module github.com/you/slug
├── slug.go       package slug
├── slug_test.go
├── example_test.go
└── internal/
    └── table/    helpers the public package uses, hidden from users

Paket modül kökünde durur, bu yüzden kullanıcılar github.com/you/slug'ı import eder ve slug.Make(...)'i çağırır. Genel API olarak desteklemek istemediğiniz her şey internal/ altına gider.

Bir servis ya da birkaç binary

shop/
├── go.mod                module github.com/you/shop
├── cmd/
│   ├── shop-api/
│   │   └── main.go       package main: flags, config, wiring
│   └── shop-worker/
│       └── main.go
├── internal/
│   ├── order/            package order: domain types and logic
│   │   ├── order.go
│   │   └── order_test.go
│   ├── store/            package store: database access
│   └── httpapi/          package httpapi: handlers, routing
├── migrations/
└── README.md

cmd/<name>/main.go her binary için bir dizin tutar ve dizin adı binary adı olur (go build ./cmd/shop-api, shop-api üretir). Her main ince kalır: yapılandırmayı okur, bağımlılıkları oluşturur, sunucuyu başlatır. Asıl kod internal/ altındaki paketlerde yaşar; orada iki binary de onu kullanabilir, başka hiçbir modül kullanamaz.

Çoğu Go servisinin ulaştığı düzen budur. Buna ilk gün değil, ikinci bir binary'niz ya da paketleri bölmek için gerçek bir nedeniniz olduğunda başvurun.

internal/ go komutu tarafından zorlanır

internal/ altındaki bir paket yalnızca internal'ın ebeveyninde kök salan ağaçtaki kod tarafından import edilebilir. Başka bir modülden derleme başarısız olur:

package example.com/b
	main.go:6:2: use of internal package example.com/a/internal/secret not allowed

Bu, internal/'ı API yüzeyinizi küçük tutmanın aracı yapar. Oradaki kod serbestçe değişebilir, çünkü her çağıranın kendi deponuzda olduğunu bilirsiniz. Bir uygulama için neredeyse her şeyi internal/'a koymak makuldür. Bir kütüphane için ise kararlı tutmaya söz verdiğinizi vermediğinizden ayırır.

internal her derinlikte çalışır: shop/internal/order tüm shop/ için görünürken, shop/internal/order/internal/pricing yalnızca shop/internal/order/ içinde görünür.

Paketleri adlandırmak

Paket adı her çağrı noktasının bir parçasıdır, bu yüzden dizin ağacından daha önemlidir.

  • Kısa, küçük harfli, tek kelime: order, store, httpapi. Alt çizgi yok, mixedCaps yok.
  • Onu içerdiğiyle değil, sağladığıyla adlandırın. util, common, helpers, misc ve models hiçbir şey söylemez ve çöplüğe dönüşür; Go ekibinin stil rehberi bunları açıkça önermez. Bir yardımcıyı onu kullanan pakete ya da amacıyla adlandırılmış bir pakete (slug, retry) koyun.
  • Tekrardan (stutter) kaçının. Çağıranlar order.Order ve order.New yazar, bu yüzden şeylere order.OrderService ya da order.NewOrder adını vermeyin. Standart kütüphane bunu tutarlı biçimde yapar: http.HTTPServer değil, http.Server.
  • Dizin adı ile paket adı eşleşmelidir, komut dizinlerindeki package main dışında. Farklı olduklarında okuyanlar import'un hangi tanımlayıcıyı getirdiğini öğrenmek için bir dosya açmak zorunda kalır.

Paketleri türe göre değil sorumluluğa göre bölün. Bir models/, controllers/, services/ bölmesi (başka ekosistemlerde yaygın) her özelliği üç pakete dokunmaya zorlar ve Go'nun yasakladığı import döngüleri yaratma eğilimindedir. Bir alan kavramı etrafında düzenlenen paketler (order, payment, user) her biri kendi tiplerini ve mantığını bir arada tutar.

Testler nereye gider

Testler kodun yanında, aynı dizinde, *_test.go adlı dosyalarda yaşar. tests/ ağacı yoktur.

  • order_test.go içinde package order: dışa kapalı tanımlayıcıları kullanabilen bir iç test.
  • Aynı dizinde package order_test: bir çağıran gibi yalnızca dışa açık API'yi gören bir dış test. Örnekler ve testlerde import döngülerinden kaçınmak için kullanışlıdır.
  • Fixture dosyaları testlerin yanındaki bir testdata/ dizinine gider. Go aracı onu yok sayar ve testler çalışma dizini olarak paket diziniyle çalışır, bu yüzden testdata/big.json gibi göreli yollar çalışır.

Kökteki diğer dosyalar

Dosya ya da dizinAmaç
go.mod, go.summodül tanımı ve bağımlılık checksum'ları, her zaman kökte
README.md, LICENSEher projede olduğu gibi
Makefile ya da Taskfile.ymlisteğe bağlı build kısayolları
Dockerfileservisler için genellikle kökte
migrations/, web/, docs/tuttuklarıyla adlandırılmış Go dışı varlıklar
tools.goaraç bağımlılıklarını sabitlemenin eski yolu; Go 1.24 onun yerine go.mod içinde tool satırlarını getirir (go get -tool)
go.workbirkaç modülü yerel olarak birlikte geliştirmek için bir workspace; tek modüllü projelerde genellikle commit edilmez

Sık yapılan hatalar

  • Küçük bir proje için büyük bir şablon kopyalamak. Düz başlayın ve ikinci bir binary ya da gerçek bir sınır ortaya çıktığında dizin ekleyin.
  • utils ya da common adlı paketler. Paketleri yaptıkları işle adlandırın.
  • Dosya ya da tip başına bir paket. Go paketleri Java sınıflarından daha büyük birimler olarak tasarlanmıştır. On dosyalı bir paket normaldir.
  • Import döngüleri. İki paket birbirini import edemez. Bu genellikle birbirlerine ait olduklarını, paylaşılan bir tipin daha alt seviyeli bir pakete taşınması gerektiğini ya da bir tarafın diğer paket yerine küçük bir interface'e bağımlı olması gerektiğini gösterir.
  • Refleks olarak pkg/. Anlam eklemeden her import yoluna bir parça ekler.
  • Ayrı bir dizinde testler. Orada dışa kapalı kodu göremezler ve araçlar onları orada beklemez.

Sıkça Sorulan Sorular

Resmi bir Go proje düzeni var mı?

Zorunlu bir tane yok. Go ekibi go.dev/doc/modules/layout adresinde birkaç yaygın biçimi (tek bir paket, bir komut, internal/ ile birkaç komut) anlatan bir rehber yayımlar. Popüler golang-standards/project-layout deposu bir Go standardı değil, bir topluluk projesidir ve Go ekibi bunu açıkça söylemiştir.

Go'da internal dizini nedir?

Import yolu bir internal elemanı içeren bir paket yalnızca o internal dizininin ebeveyninde kök salan kod tarafından import edilebilir. example.com/app/internal/store, example.com/app/ altındaki her yerden import edilebilir ve go komutu diğer herhangi bir modülden gelen import'ları use of internal package ... not allowed ile reddeder.

Go'da pkg dizini kullanmalı mıyım?

İsteğe bağlıdır ve anlam eklemeden bir yol parçası ekler. Bazı büyük projeler genel kütüphane kodunu geri kalanından ayırmak için pkg/ kullanır, ama Go standart kütüphanesi ve çoğu modern proje kullanmaz. Import edilebilir paketleri modül köküne ya da adlandırılmış dizinlere, özel olan her şeyi internal/'a koyun.

Bir Go projesinde test dosyaları nereye konur?

Test ettikleri kodla aynı dizine, *_test.go adıyla. Go'da ayrı bir test ağacı yoktur. Fixture dosyaları testlerin yanındaki bir testdata dizinine gider; go aracı paket ararken onu yok sayar.

Coddy programming languages illustration

Coddy ile kodlamayı öğren

BAŞLA