Project Layout
project/
├── cmd/ # Entry point per binary
│ └── server/
│ └── main.go
├── internal/ # Private code
│ ├── domain/ # Entity, use case interface
│ ├── application/ # Service, DTO
│ ├── infrastructure/ # Handler, repo, middleware
│ └── config/ # Config loader
├── pkg/ # Shared, reusable code
├── migrations/ # DB migrations (atlas, goose)
├── tests/ # Integration tests
├── Makefile
├── go.mod
└── go.sum
Dependency Injection
Gunakan uber-go/fx untuk wiring otomatis.
func main() {
fx.New(
config.Module,
database.Module,
modules.UserModule,
fx.Invoke(func(*server.Server) {}),
).Run()
}
API Handler (Chi)
func (h *Handler) CreateUser(w http.ResponseWriter, r *http.Request) {
var req CreateUserRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
respond.Error(w, http.StatusBadRequest, "invalid body")
return
}
user, err := h.service.Create(r.Context(), req)
if err != nil {
respond.Error(w, http.StatusInternalServerError, err.Error())
return
}
respond.OK(w, user)
}
Error Handling
// Return early
if err != nil {
return fmt.Errorf("failed to create user: %w", err)
}
// Wrap context
return fmt.Errorf("user %s: %w", userID, ErrNotFound)
Testing
- Unit test:
go test ./internal/... - Integration test:
go test ./tests/... -tags integration - Coverage target: > 70%
func TestCreateUser(t *testing.T) {
repo := newMockUserRepo()
svc := NewUserService(repo)
user, err := svc.Create(ctx, CreateUserRequest{Name: "Khoirul"})
assert.NoError(t, err)
assert.Equal(t, "Khoirul", user.Name)
}
Tools
| Tool | Command | Fungsi |
|---|---|---|
| Air | air | Hot reload |
| Delve | dlv debug | Debugger |
| golangci-lint | golangci-lint run ./... | Comprehensive lint |
| goimports | goimports -w . | Auto import sort |
Orm / Query Builder
- Ent (Facebook): Type-safe, generated code
- GORM: Prototyping dan rapid dev
- SQLx: Raw SQL dengan scanning
- Raw SQL: Untuk query kompleks / performance critical
Config
Gunakan struct tag + environment variables:
type Config struct {
Port int `env:"PORT,default=8080"`
DBHost string `env:"DB_HOST,required"`
LogLevel string `env:"LOG_LEVEL,default=info"`
}