Module Boundary

Goals

  • Keep each package easy to explain in one sentence.
  • Minimize change impact by shrinking the public surface.

Guidance

  • Expose one obvious primary entry point (exported type, interface, or function).
  • Keep non-entry helpers and assembly details unexported.
  • Export additional symbols only as stable domain contracts.
  • Prefer deep modules with narrow APIs over wide public surfaces.
  • Keep interfaces behavior-focused; hide concrete infrastructure behind constructors and internal wiring.
  • For wide interfaces, define them at the package entry surface (for example, api.go) and comment every method with behavior and caller expectations.
  • Extract reusable pure helpers into domain-named xxxutil packages; avoid generic names like common.
  • Keep xxxutil stateless.
  • Use a dedicated Builder when parameters are complex or need cross-field validation.
  • One package = one domain responsibility. Split monolithic packages along domain seams.
  • Inline adapters when they have a single consumer; indirection should earn its keep.

Single Responsibility Examples

  • Initialization vs. serving: Wiring dependencies and starting background loops belongs separate from request handling.
  • Protocol translation vs. domain logic: gRPC/HTTP handlers translate wire formats; business decisions live in injected domain services.
  • State lifecycle vs. business operations: A manager that tracks resources by ID is distinct from the logic that operates on those resources.
  • Reusable utility vs. domain package: Pure, stateless helpers shared across packages belong in a dedicated xxxutil; domain-aware logic stays in its owning package.

Deep vs Wide Interfaces

  • Deep interfaces: few operations, business semantics, stable over time.
  • Wide interfaces: many operations, adapter semantics, integration detail exposure.
  • Keep deep interfaces in core packages and wide interfaces at boundaries.

Review Bullets

  • Can a new reader identify package responsibility in one sentence?
  • Is there one obvious primary entry point?
  • Is every additional exported symbol a stable contract?
  • If a wide interface exists, is it defined at the package entry surface and does every method have a behavior comment?
  • Are helper packages domain-explicit (for example, sliceutil) instead of generic names like common?
  • If an xxxutil package exists, is it stateless?
  • If parameters are complex or coupled, would a Builder make construction clearer and safer?

Sources & References