Packages, Modules, Visibility, dan Dependency Boundaries
Materi mendalam tentang package, module, exported identifier, internal package, go.mod, go.sum, workspace, dan dependency boundary dalam Go.
Packages, Modules, Visibility, dan Dependency Boundaries
Posisi Part Ini dalam Framework Kaufman
Setelah memahami value, pointer, dan memory boundary, sekarang kita naik satu level: package dan module boundary.
Banyak programmer bisa menulis function Go yang benar, tetapi kesulitan membuat codebase Go yang mudah tumbuh. Penyebabnya sering bukan syntax, tetapi boundary:
- Package terlalu besar.
- Package terlalu kecil.
- Interface diletakkan di tempat yang salah.
- Dependency direction kacau.
- Import cycle muncul.
internaltidak dimanfaatkan.go.moddiperlakukan sebagai file ajaib.replacedibiarkan masuk ke production.- Module dipecah terlalu cepat.
Dalam framework Kaufman, part ini adalah tahap deconstruction untuk skill desain codebase Go.
Target Setelah Part Ini
Setelah part ini, kamu harus bisa:
- Membedakan package, module, repository, dan binary.
- Mendesain package boundary yang stabil.
- Memakai exported/unexported identifiers secara sengaja.
- Menggunakan
internaluntuk membatasi akses. - Membaca dan merawat
go.modsertago.sum. - Memahami
require,replace,exclude,retract, dan workspace. - Menghindari import cycle dengan dependency direction yang sehat.
- Mendesain struktur project Go untuk service produksi.
- Menentukan kapan perlu multi-module dan kapan cukup single module.
- Membuat dependency boundary yang mendukung testing, refactoring, dan operability.
1. Mental Model: Package Adalah Unit Desain Utama Go
Di Go, package bukan sekadar folder. Package adalah unit:
- Compilation
- Visibility
- Documentation
- Testing
- API
- Dependency
- Review
- Ownership
Setiap file .go dalam directory yang sama harus memiliki package name yang sama, kecuali file test eksternal dengan suffix _test.
Contoh:
billing/
invoice.go
payment.go
tax.go
Semua file:
package billing
Dari luar package, user memakai exported identifiers:
invoice, err := billing.NewInvoice(...)
Package yang baik biasanya punya:
- Nama singkat.
- Satu alasan untuk berubah.
- API kecil.
- Internal detail tersembunyi.
- Test yang menjelaskan behavior.
- Dependency yang masuk akal.
1.1 Package Bukan Layer Otomatis
Ini struktur yang sering salah:
project/
models/
services/
repositories/
handlers/
utils/
Masalah:
- Package dinamai berdasarkan technical layer, bukan domain capability.
modelsjadi tempat semua orang menaruh data.utilsjadi tempat sampah.- Dependency mudah menyilang.
- Boundary domain lemah.
Lebih baik mulai dari capability:
project/
order/
payment/
customer/
notification/
Atau untuk service kecil:
project/
internal/
order/
payment/
platform/
Go cenderung lebih sehat jika package merepresentasikan cohesive behavior, bukan sekadar kategori teknis.
2. Package Name vs Import Path vs Module Path
Ini tiga konsep berbeda.
2.1 Package Name
Ditulis di atas file:
package billing
Package name adalah nama yang dipakai ketika code di-import.
billing.NewInvoice(...)
2.2 Import Path
Path lengkap untuk mengimpor package:
import "github.com/acme/shop/internal/billing"
2.3 Module Path
Didefinisikan di go.mod:
module github.com/acme/shop
Import path package biasanya:
<module-path>/<subdirectory>
Contoh:
module path: github.com/acme/shop
directory: internal/billing
import path: github.com/acme/shop/internal/billing
package name: billing
3. Exported dan Unexported Identifiers
Go memakai huruf pertama untuk visibility:
UserexportedNewUserexporteduserunexportednewUserunexported
Contoh:
package account
type Account struct {
id string
balance int64
}
func NewAccount(id string) *Account {
return &Account{id: id}
}
func (a *Account) Balance() int64 {
return a.balance
}
Dari luar package:
acc := account.NewAccount("acc-1")
fmt.Println(acc.Balance())
// acc.balance // compile error
Visibility di Go adalah desain API.
3.1 Export Sekecil Mungkin
Jangan export hanya karena “mungkin nanti perlu”.
Buruk:
type Account struct {
ID string
Balance int64
Status string
}
Semua field bisa diubah dari luar. Invariant hilang.
Lebih baik:
type Account struct {
id string
balance int64
status Status
}
func (a *Account) ID() string {
return a.id
}
func (a *Account) Balance() int64 {
return a.balance
}
Export adalah komitmen. Setelah dipakai package lain, mengubahnya bisa menjadi breaking change.
4. Package API Surface
Package API surface adalah semua exported identifiers:
- Exported type
- Exported function
- Exported method
- Exported const
- Exported var
- Exported field
- Exported interface
Semakin besar surface, semakin mahal maintenance.
Tujuan desain package:
Buat exported API sekecil mungkin, tetapi cukup ekspresif untuk use case nyata.
5. Naming Package
Package name harus:
- Singkat.
- Lowercase.
- Tidak pakai underscore.
- Tidak redundant dengan parent path.
- Menjelaskan konsep, bukan implementasi berlebihan.
- Enak dibaca di call site.
Buruk:
import "github.com/acme/shop/internal/order/orderutils"
orderutils.CalculateOrderTotal(order)
Lebih baik:
import "github.com/acme/shop/internal/order"
order.Total()
Atau:
import "github.com/acme/shop/internal/pricing"
pricing.Calculate(order)
5.1 Hindari Package Stutter
Jika package bernama user, jangan export type UserService hanya karena kebiasaan.
user.UserService
user.UserRepository
user.UserModel
Kadang masih masuk akal, tapi sering stutter.
Lebih baik:
user.Service
user.Repository
user.Record
Atau lebih domain-specific:
identity.Service
identity.Store
identity.Profile
Call site matters.
6. internal Package
Directory bernama internal punya aturan khusus: package di dalamnya hanya bisa di-import oleh code yang berada di parent tree dari internal.
Contoh:
github.com/acme/shop/
internal/
billing/
payment/
cmd/
api/
Package github.com/acme/shop/internal/billing bisa di-import oleh:
github.com/acme/shop/cmd/api
github.com/acme/shop/internal/payment
github.com/acme/shop/internal/...
Tetapi tidak bisa di-import oleh module luar:
github.com/other/app
internal adalah cara Go memberi encapsulation pada level repository/module.
6.1 Kapan Memakai internal
Gunakan internal untuk:
- Application code yang bukan library public.
- Adapter database.
- HTTP handler internal.
- Implementation detail.
- Package yang boleh berubah tanpa compatibility guarantee.
- Service production yang tidak dimaksudkan sebagai reusable SDK.
Contoh struktur:
shop/
go.mod
cmd/
api/
main.go
internal/
order/
order.go
service.go
store.go
payment/
payment.go
platform/
postgres/
httpserver/
Untuk sebagian besar backend service, default yang sehat adalah: taruh implementation di internal.
7. pkg Directory: Jangan Otomatis
Banyak repo Go lama memakai:
pkg/
pkg bukan keyword khusus di Go. Ini hanya convention komunitas.
Gunakan pkg jika:
- Package memang dimaksudkan untuk di-import oleh repo/module lain.
- API-nya punya compatibility expectation.
- Kamu siap mendokumentasikan dan mempertahankannya.
Jangan gunakan pkg hanya karena template.
Untuk service internal, sering lebih baik:
internal/
daripada:
pkg/
Karena internal memberi enforcement compiler-level.
8. Module: Unit Versioning dan Dependency Management
Module adalah kumpulan package yang versioned bersama.
File utama:
go.mod
Contoh:
module github.com/acme/shop
go 1.26
require (
github.com/jackc/pgx/v5 v5.7.0
github.com/go-chi/chi/v5 v5.2.0
)
Module menjawab:
- Ini project apa?
- Path import root-nya apa?
- Minimal Go version/language version apa?
- Dependency apa yang dibutuhkan?
- Versi dependency mana yang dipakai?
8.1 Repository vs Module
Satu repository bisa punya satu module:
shop/
go.mod
cmd/
internal/
Atau beberapa module:
platform/
service-a/
go.mod
service-b/
go.mod
libs/
auth/
go.mod
Default untuk mulai:
Satu repository, satu module.
Pecah menjadi multi-module hanya jika ada alasan kuat:
- Release cadence berbeda.
- Ownership berbeda.
- Dependency graph terlalu berat.
- Library benar-benar dipakai banyak service.
- Compatibility boundary dibutuhkan.
- Build/test isolation sangat penting.
Multi-module terlalu awal meningkatkan overhead.
9. go.mod
go.mod bukan file yang diedit sembarangan setiap saat. Ia adalah kontrak module.
Directive umum:
| Directive | Fungsi |
|---|---|
module | path module |
go | minimum language/toolchain semantics |
require | dependency module |
replace | mengganti source dependency |
exclude | melarang versi tertentu |
retract | menyatakan versi module tidak seharusnya dipakai |
toolchain | menyarankan toolchain Go tertentu |
godebug | default GODEBUG behavior untuk main package/module |
Contoh:
module github.com/acme/shop
go 1.26
require github.com/google/uuid v1.6.0
9.1 go mod tidy
Gunakan:
go mod tidy
Untuk:
- Menambah dependency yang benar-benar dipakai.
- Menghapus dependency yang tidak dipakai.
- Memperbarui
go.sum. - Menjaga module graph tetap bersih.
Praktik sehat:
go test ./...
go mod tidy
git diff -- go.mod go.sum
Pastikan perubahan dependency memang disengaja.
10. go.sum
go.sum berisi checksum cryptographic untuk module content yang dipakai atau dibutuhkan untuk verifikasi.
Kesalahan umum:
“go.sum itu lock file.”
Tidak persis. go.sum bukan lock file seperti package-lock.json. Go memakai minimal version selection dan module graph untuk menentukan versi. go.sum membantu verifikasi integritas module.
Praktik:
- Commit
go.sum. - Jangan edit manual.
- Biarkan
go mod tidydangocommand mengelolanya. - Review perubahan
go.sumsaat dependency berubah.
11. Minimal Version Selection
Go modules menggunakan pendekatan minimal version selection.
Mental model sederhana:
- Setiap module menyatakan minimum version dependency yang dibutuhkan.
- Build memilih versi minimum yang memenuhi seluruh requirement.
- Go tidak otomatis memilih versi terbaru hanya karena tersedia.
Contoh:
main requires A v1.2.0
B requires A v1.4.0
selected A = v1.4.0
Ini membuat build lebih predictable dibanding resolver yang selalu mencari versi tertinggi berdasarkan range fleksibel.
12. Semantic Import Versioning
Untuk major version v2 ke atas, module path harus memuat suffix major version.
Contoh:
module github.com/acme/lib/v2
Import:
import "github.com/acme/lib/v2"
Kenapa? Karena v2 dianggap breaking API. Dengan suffix path, Go memungkinkan v1 dan v2 coexist dalam build yang sama.
Contoh:
import (
libv1 "github.com/acme/lib"
libv2 "github.com/acme/lib/v2"
)
13. replace: Berguna, tapi Berbahaya Jika Lupa
replace mengalihkan dependency ke path atau versi lain.
Contoh lokal:
replace github.com/acme/payments => ../payments
Berguna untuk development multi-repo lokal.
Tapi berbahaya jika masuk production tanpa sengaja:
- Build orang lain bisa gagal karena path lokal tidak ada.
- CI tidak reproducible.
- Release artifact tergantung state lokal.
- Dependency graph tidak jelas.
Praktik:
- Pakai
replacelokal hanya sementara. - Review
go.modsebelum commit. - Untuk multi-module lokal, pertimbangkan
go work. - Untuk fork production, dokumentasikan alasannya dengan jelas.
14. Workspace dengan go.work
Workspace memungkinkan beberapa module lokal dipakai bersama tanpa mengubah go.mod masing-masing.
Contoh:
go work init ./shop ./payments
Menghasilkan:
go 1.26
use (
./shop
./payments
)
Gunakan workspace untuk:
- Mengembangkan beberapa module lokal bersamaan.
- Menghindari
replacelokal digo.mod. - Memudahkan monorepo multi-module.
- Testing integrasi antar module lokal.
Praktik:
- Jangan otomatis commit
go.workjika isinya spesifik environment developer. - Commit
go.workhanya jika workspace adalah bagian resmi workflow repo.
15. Private Modules
Untuk private module, Go command perlu tahu domain mana yang privat agar tidak mencoba memakai public proxy/checksum database.
Environment penting:
go env -w GOPRIVATE=github.com/acme/*
Bisa juga:
export GOPRIVATE=github.com/acme/*
Pertimbangkan juga:
- Access token Git.
- CI credentials.
GONOSUMDBGONOPROXY- Corporate module proxy.
Security point:
Jangan membocorkan private module path ke public proxy jika organisasi melarangnya.
16. Dependency Direction
Dependency direction harus mudah digambar.
Buruk:
Repository --> Service menciptakan cycle.
Lebih baik:
Namun di Go, implementasi interface tidak perlu import interface secara eksplisit. Pattern yang lebih umum:
internal/order/
service.go // defines Store interface needed by Service
internal/platform/postgres/
order_store.go // implements order.Store
cmd/api/
main.go // wires them together
Dependency:
order tidak import postgres.
Domain/application package mendefinisikan interface kecil yang dibutuhkannya. Adapter package mengimplementasikan.
17. Import Cycle
Go melarang import cycle.
Contoh:
order imports payment
payment imports order
Ini compile error.
Import cycle biasanya sinyal desain:
- Boundary salah.
- Shared type diletakkan di package yang salah.
- Dua package terlalu saling tahu.
- Interface ditempatkan di provider, bukan consumer.
- Utility package mulai mengimpor domain.
17.1 Cara Memecah Import Cycle
Strategi:
- Pindahkan interface ke consumer.
- Pindahkan shared value object ke package yang lebih netral.
- Gabungkan package jika memang cohesion tinggi.
- Balik dependency dengan function parameter atau interface kecil.
- Pindahkan wiring ke
cmdatau package composition root.
Contoh salah:
payment.Service depends on order.Service
order.Service depends on payment.Service
Solusi desain:
order.Service depends on PaymentAuthorizer interface
payment.Adapter implements authorization
cmd/api wires order.Service with payment.Adapter
18. Interface Placement
Rule praktis Go:
Interface biasanya didefinisikan di package yang menggunakannya, bukan package yang mengimplementasikannya.
Buruk:
// package postgres
type Store interface {
SaveOrder(ctx context.Context, order order.Order) error
}
Ini membuat package postgres mendikte abstraction.
Lebih baik:
// package order
type Store interface {
Save(ctx context.Context, o Order) error
}
Lalu:
// package postgres
type OrderStore struct {
db *sql.DB
}
func (s *OrderStore) Save(ctx context.Context, o order.Order) error {
return nil
}
postgres.OrderStore tidak perlu mendeklarasikan bahwa ia implement order.Store. Go akan mengecek implicit satisfaction saat wiring atau test.
18.1 Compile-time Interface Assertion
Kadang berguna untuk menjaga adapter tetap memenuhi contract:
var _ order.Store = (*OrderStore)(nil)
Gunakan secukupnya. Ini berguna di adapter boundary.
19. Struktur Project Service Go
Tidak ada satu struktur resmi untuk semua kasus. Tetapi untuk backend service production, struktur berikut sering sehat:
shop/
go.mod
go.sum
README.md
Makefile
cmd/
api/
main.go
migrate/
main.go
internal/
order/
order.go
service.go
store.go
errors.go
service_test.go
payment/
payment.go
service.go
transport/
http/
server.go
order_handler.go
middleware.go
platform/
postgres/
db.go
order_store.go
config/
config.go
logger/
logger.go
migrations/
001_init.sql
testdata/
Prinsip:
cmdberisi entrypoint executable.internalberisi application implementation.- Domain/application package tidak tergantung transport/database.
- Adapter package tergantung domain/application package.
- Wiring dilakukan di
main. - Migration dan deployment artifact jelas.
20. Composition Root
main.go seharusnya banyak melakukan wiring, bukan business logic.
func main() {
cfg := config.MustLoad()
log := logger.New(cfg.LogLevel)
db, err := postgres.Open(cfg.DatabaseURL)
if err != nil {
log.Fatal(err)
}
defer db.Close()
orderStore := postgres.NewOrderStore(db)
orderService := order.NewService(orderStore)
server := httptransport.NewServer(orderService, log)
if err := server.Run(cfg.HTTPAddr); err != nil {
log.Fatal(err)
}
}
Business logic tetap di package application/domain.
Composition root:
- Membuat dependency concrete.
- Menghubungkan interface ke implementation.
- Mengatur lifecycle.
- Mengatur configuration.
- Menjadi tempat dependency graph terlihat.
21. Package Documentation
Package yang exported harus punya doc comment.
File:
// Package order contains application logic for creating, paying,
// and canceling customer orders.
package order
Exported identifier:
// Service coordinates order use cases.
type Service struct {
store Store
}
// NewService creates an order service using store for persistence.
func NewService(store Store) *Service {
return &Service{store: store}
}
Doc comment bukan formalitas. Ia menjelaskan contract.
22. Testing Package Boundary
Ada dua style test:
22.1 Same-package Test
package order
Bisa access unexported identifiers. Cocok untuk internal behavior.
22.2 External Test Package
package order_test
Harus import package seperti user luar:
import "github.com/acme/shop/internal/order"
Cocok untuk menguji public API package.
Gunakan external test untuk memastikan exported API nyaman dipakai.
23. Avoid utils
Package utils sering menjadi tempat segala hal.
Buruk:
utils/
string.go
date.go
retry.go
validation.go
http.go
Masalah:
- Tidak cohesive.
- Nama call site buruk.
- Dependency mudah membesar.
- Menjadi dumping ground.
Lebih baik:
retry/
clock/
validation/
httperror/
Atau jika helper hanya dipakai satu package, simpan sebagai unexported function di package itu.
24. Avoid God Package
Package terlalu besar:
app/
account.go
order.go
payment.go
shipping.go
notification.go
report.go
Masalah:
- Build unit terlalu besar.
- Test sulit fokus.
- API surface membengkak.
- Ownership kabur.
- Dependency internal kacau.
Pecah berdasarkan cohesion:
account/
order/
payment/
shipping/
notification/
report/
Tetapi jangan terlalu cepat memecah. Awali sederhana, pecah ketika boundary mulai terlihat.
25. Package Size Heuristic
Package terlalu kecil jika:
- Hanya punya satu file dengan satu type trivial.
- Nama package tidak menambah makna.
- Banyak import cycle muncul karena fragmentasi.
- Caller harus import banyak package untuk satu use case.
- Abstraction lebih banyak dari behavior.
Package terlalu besar jika:
- Banyak alasan berubah.
- Banyak dependency tidak relevan untuk sebagian fitur.
- Test setup berat untuk function sederhana.
- API surface membingungkan.
- File saling terkait lemah.
Heuristic:
Package ideal cukup besar untuk punya cohesion, cukup kecil untuk punya purpose jelas.
26. Dependency Hygiene
Command penting:
go list -m all
go mod why -m github.com/some/dependency
go mod graph
go mod tidy
Gunakan untuk menjawab:
- Kenapa dependency ini ada?
- Siapa yang menarik dependency ini?
- Apakah dependency langsung atau transitive?
- Apakah versi dependency berubah?
- Apakah dependency masih dipakai?
26.1 Dependency Review Checklist
Sebelum menambah dependency:
- Apakah standard library cukup?
- Apakah dependency aktif dipelihara?
- Apakah API kecil dan stabil?
- Apakah lisensi cocok?
- Apakah dependency menarik banyak transitive dependency?
- Apakah dependency dipakai di hot path?
- Apakah security posture jelas?
- Apakah bisa diganti mudah?
- Apakah dependency worth it dibanding implementasi sendiri?
Untuk Go, standard library sangat kuat. Jangan menambah dependency untuk hal kecil tanpa alasan.
27. Build Tags
Build tags bisa mengatur file mana yang dikompilasi.
Contoh:
//go:build integration
package postgres
File ini hanya ikut jika build tag integration aktif:
go test -tags=integration ./...
Gunakan untuk:
- Integration tests.
- OS-specific implementation.
- Optional feature.
- Expensive test suite.
Jangan gunakan build tags untuk menyembunyikan desain dependency yang kacau.
28. Generated Code Boundary
Generated code umum di Go:
- Protobuf/gRPC
- Mock
- SQL codegen
- OpenAPI client/server
- Serialization
Praktik:
- Pisahkan generated code.
- Jangan edit manual.
- Commit jika workflow organisasi membutuhkannya.
- Pastikan generation reproducible.
- Jangan biarkan generated DTO mencemari domain model.
Contoh:
internal/
order/
order.go
gen/
proto/
order.pb.go
Atau:
api/
proto/
generated/
internal/
Boundary penting: generated type sering bukan domain type.
29. Case Study: Dari Struktur Buruk ke Struktur Sehat
29.1 Struktur Awal
shop/
main.go
models/
order.go
payment.go
services/
order_service.go
payment_service.go
repositories/
order_repository.go
utils/
db.go
response.go
validation.go
Masalah:
modelsdipakai semua package.utilsmenampung banyak concern.- Repository tahu terlalu banyak tentang service.
- HTTP response helper bisa masuk ke domain.
- Testing service butuh database concrete.
29.2 Struktur Refactor
shop/
cmd/
api/
main.go
internal/
order/
order.go
service.go
store.go
errors.go
payment/
payment.go
service.go
transport/
http/
server.go
order_handler.go
response.go
platform/
postgres/
db.go
order_store.go
validation/
validation.go
Dependency flow:
order tidak tahu HTTP. order tidak tahu Postgres. Adapter tahu domain, bukan sebaliknya.
30. Practical Module Workflow
Untuk project baru:
mkdir shop
cd shop
go mod init github.com/acme/shop
mkdir -p cmd/api internal/order
Tambahkan code.
Jalankan:
go test ./...
go mod tidy
Lihat dependency:
go list -m all
Upgrade dependency:
go get github.com/google/uuid@latest
go mod tidy
go test ./...
Downgrade atau pin:
go get github.com/google/uuid@v1.5.0
31. Versioning Internal API
Walaupun package berada dalam satu module, API internal tetap perlu disiplin.
Untuk exported type dalam internal:
type Service struct {
store Store
}
Ini exported agar package lain dalam module bisa memakainya. Tetapi masih internal terhadap dunia luar.
Jaga tetap bersih:
- Jangan export field jika tidak perlu.
- Constructor validasi dependency.
- Method merepresentasikan use case.
- Error contract jelas.
- Interface kecil.
- Test public behavior.
32. Public Library vs Internal Service
32.1 Public Library
Karakter:
- API compatibility penting.
- Documentation wajib kuat.
- Breaking change mahal.
- Versioning harus disiplin.
- Surface area kecil.
- Tidak boleh bergantung pada environment spesifik.
32.2 Internal Service
Karakter:
- Optimized untuk delivery dan operability.
internallebih dominan.- API package bisa berubah selama semua caller dalam repo ikut berubah.
- Boundary tetap penting untuk test dan maintenance.
- Versioning module mungkin hanya untuk build/release service.
Jangan mendesain internal service seolah-olah semua package adalah public SDK.
33. Anti-pattern Umum Package dan Module
33.1 Interface di Mana-mana
type UserService interface {
CreateUser(...)
UpdateUser(...)
DeleteUser(...)
GetUser(...)
ListUsers(...)
}
Jika hanya ada satu implementation dan interface tidak dibutuhkan consumer, jangan buat dulu.
33.2 Package common
common sering menjadi utils versi enterprise.
Lebih baik nama berdasarkan behavior.
33.3 Exported Global Variable
var DB *sql.DB
Masalah:
- Sulit dites.
- Lifecycle tidak jelas.
- Race risk.
- Hidden dependency.
Lebih baik inject dependency.
33.4 replace Lokal Tertinggal
replace github.com/acme/lib => ../lib
CI atau teammate bisa rusak.
33.5 Multi-module Prematur
Memecah module terlalu awal membuat:
- Testing lebih rumit.
- Versioning lebih berat.
- Refactoring lebih mahal.
- Dependency management lebih sulit.
34. Latihan Terarah
Latihan 1 — Package Boundary
Buat module:
github.com/example/bank
Struktur:
cmd/api
internal/account
internal/platform/memory
account berisi:
AccountServiceStoreinterface
memory berisi implementation account.Store.
cmd/api melakukan wiring.
Latihan 2 — Internal Enforcement
Buat package:
internal/secret
Coba import dari module lain. Amati compile error.
Latihan 3 — Interface Placement
Buat desain awal di mana postgres mendefinisikan interface. Refactor agar order sebagai consumer mendefinisikan interface kecil.
Latihan 4 — Dependency Audit
Jalankan:
go list -m all
go mod graph
go mod why -m <module>
Jawab:
- Dependency mana yang direct?
- Dependency mana yang transitive?
- Dependency mana yang tidak kamu pahami?
- Apakah ada dependency yang bisa dihapus?
Latihan 5 — Workspace
Buat dua module lokal:
shop/
payments/
Gunakan:
go work init ./shop ./payments
Buat shop meng-import package dari payments. Jalankan test dari root workspace.
35. Checklist Review Part 08
Gunakan checklist ini saat review codebase Go:
- Apakah package punya purpose jelas?
- Apakah package name singkat dan enak di call site?
- Apakah exported API minimal?
- Apakah field struct diexport hanya jika memang aman?
- Apakah package
internaldipakai untuk implementation detail? - Apakah
pkgdipakai hanya untuk API yang memang public/reusable? - Apakah dependency direction bisa digambar sederhana?
- Apakah domain/application bebas dari transport/database detail?
- Apakah interface diletakkan di consumer?
- Apakah import cycle tidak muncul?
- Apakah
go.modbersih setelahgo mod tidy? - Apakah
go.sumdi-commit? - Apakah
replacelokal tidak tertinggal? - Apakah private module config aman?
- Apakah generated code tidak bocor ke domain model?
- Apakah
utils/commondihindari atau diberi nama lebih spesifik? - Apakah composition root jelas?
- Apakah package punya tests yang mencerminkan public behavior?
36. Ringkasan Mental Model
Go codebase yang sehat bukan yang punya folder paling banyak. Go codebase yang sehat adalah yang dependency boundary-nya jelas, API surface-nya kecil, dan package-nya punya cohesion yang kuat.
Package adalah alat desain.
Module adalah alat versioning dan dependency.
internal adalah alat enforcement.
go.mod adalah kontrak dependency.
Boundary yang baik membuat code mudah dites, mudah direview, mudah diubah, dan lebih aman ketika sistem tumbuh.
You just completed lesson 08 in build core. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.
Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.