Section 19
Project Layout
comms engine — standard Go project layout
engine/
├── cmd/
│ └── engine/
│ └── main.go // entry point — wires everything
├── internal/ // private — CANNOT be imported outside this module
│ ├── domain/ // pure data types: Message, Event, Route
│ │ ├── message.go
│ │ └── message_easyjson.go // generated by easyjson
│ ├── store/ // database layer
│ │ ├── queries.sql.go // generated by sqlc — do not edit
│ │ ├── pg_store.go // wraps sqlc queries, implements interface
│ │ └── interfaces.go // store interfaces defined here
│ ├── broker/ // message broker abstraction
│ │ ├── redis_broker.go // production
│ │ └── mem_broker.go // in-memory for tests
│ ├── engine/ // orchestration core
│ │ ├── engine.go // Engine struct, Start/Stop
│ │ ├── router.go // topic routing logic
│ │ └── worker.go // worker goroutine pool
│ └── handler/ // HTTP handlers
├── pkg/ // public — importable by external modules
│ └── middleware/
├── go.mod // module definition (like pom.xml)
└── go.sum // dependency checksums (like lockfile)go.mod — module system
Java — pom.xml / build.gradle
<!-- pom.xml -->
<groupId>com.mycompany</groupId>
<artifactId>engine</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>32.0.0-jre</version>
</dependency>
</dependencies>Go — go.mod
module github.com/mycompany/engine
go 1.22
require (
github.com/gin-gonic/gin v1.9.1
github.com/lib/pq v1.10.9
github.com/mailru/easyjson v0.7.7
github.com/sqlc-dev/sqlc v1.25.0
)Java tool ↔ Go tool mapping
Jackson ObjectMapper / Gsonencoding/json by default; easyjson for hot-path JSON
Hibernate / JPA (ORM)GORM (closest ORM analog)
SQL-first style (jOOQ mindset)sqlc (write SQL, generate typed Go methods)
When easyjson is worth it
Start with
encoding/json. Move to easyjson only when profiling proves JSON marshalling is a bottleneck and the model shape is stable enough to justify generated code maintenance.easyjson — generated JSON marshalling
easyjson — 3-5x faster than encoding/json
// encoding/json uses reflection at runtime — allocates, slow in hot paths
// easyjson generates code at build time — no reflection, no allocations
// Step 1: annotate the file
//go:generate easyjson -all message.go
type Message struct {
ID int64 `json:"id"`
Topic string `json:"topic"`
Payload []byte `json:"payload"`
}
// Step 2: run code generation
// go generate ./...
// Step 3: easyjson creates message_easyjson.go with:
// func (m *Message) MarshalJSON() ([]byte, error) { ... }
// func (m *Message) UnmarshalJSON(data []byte) error { ... }
// Usage — same API, generated code runs automatically:
data, err := json.Marshal(&msg) // uses generated MarshalJSON
err = json.Unmarshal(data, &msg) // uses generated UnmarshalJSONWhen sqlc is better than ORM
Choose
sqlc when queries are complex, performance is critical, or you want exact SQL control. sqlc is SQL-first: it generates Go code from SQL files and schema, not from struct tags.sqlc — type-safe SQL without ORM
Java — JPA / Hibernate ORM
@Entity
@Table(name = "messages")
class Message {
@Id @GeneratedValue
private Long id;
@Column
private String topic;
}
// JPA generates SQL — you learn JPA-QL
// N+1 problems lurk
repo.findByTopic("orders");Go — sqlc (write SQL, get Go)
// Write raw SQL in queries.sql:
// -- name: GetByTopic :many
// SELECT id, topic, payload
// FROM messages
// WHERE topic = $1
// ORDER BY created_at DESC;
// Run: sqlc generate
// Creates queries.sql.go with:
func (q *Queries) GetByTopic(
ctx context.Context,
topic string,
) ([]Message, error) {
// generated — typed, no reflection
}
// Usage — fully type-safe
msgs, err := queries.GetByTopic(ctx, "orders")