Dev Day
Notes and learnings from development conferences and workshops
Below is a complete index of all available notes and documentation, organized by project. Click on any title to view its full content.
Go was designed at Google for large teams, code that lives a long time, and fast builds. Most of its decisions follow from that.
| Principle | In practice |
|---|---|
| Simplicity | Few keywords and features. If there were two ways to do something, one was removed. |
| Readability first | Code is read far more often than it is written. Clear beats clever; “boring” is a compliment. |
| Composition | No class hierarchies. You combine small types and small interfaces. |
| Explicit over magic | No annotations and no hidden control flow. Errors are ordinary return values. |
| Concurrency built in | Goroutines and channels are part of the language, not a library. |
| Fast tooling | Builds take seconds, the output is one static binary, and there is one official formatter. |
| Useful zero values | nil, "", 0, false; well-designed types work without initialisation. |
| Batteries included | A rich standard library (HTTP, JSON, crypto, testing, profiling) and small third-party libraries instead of frameworks. |
Go has types, methods and interfaces, but no classes and no inheritance. The usual concepts still have a place:
Last updated: 2026-10-04Bei keiner anderen Tätigkeit in der Architekturarbeit liegt es so nahe, Generative AI einzusetzen, wie beim Dokumentieren der Softwarelösung. Es ist ohnehin ein eher unbeliebtes Thema, die Arbeitsergebnisse sind oft textlastig — wie schön wäre es, wenn “die KI” dem Team dort das Leben erleichtern würde.
Last updated: 2026-09-25Types come after names, there are no semicolons, and nothing has to live in a class.
package domain
// CodeAlphabet omits visually ambiguous characters (0/O, 1/I/L).
const CodeAlphabet = "ABCDEFGHJKMNPQRSTUVWXYZ23456789"
const CodeLength = 6
type RandomCodes struct{}
func (RandomCodes) Generate() string {
b := make([]byte, CodeLength)
rand.Read(b)
for i := range b {
b[i] = CodeAlphabet[int(b[i])%len(CodeAlphabet)]
}
return string(b)
}
:= declares and infers the type, inside functions only.var x T declares a variable holding the zero value of T. Zero values are meant to be useful: an empty bytes.Buffer or sync.Mutex works without initialisation.Data and behaviour are declared separately. Constructors are plain functions.
Last updated: 2026-10-04Websites laden im Median über 700 KB JavaScript, im 75. Perzentil über 1,2 MB — auch dann, wenn der Inhalt zu 99 % statisch ist. Der Vortrag stellt die zentrale Frage: „Brauchen wir dafür wirklich eine SPA?" — und beantwortet sie nicht dogmatisch, sondern anhand zweier funktional identischer Rezept-Apps: einmal als klassische React-SPA, einmal mit Astro (Islands Architecture, SSG).
Last updated: 2026-09-25Failure is part of the function signature and is handled right where it happens.
code, err := s.allocateCode(ctx)
if err != nil {
return nil, fmt.Errorf("create session: %w", err)
}
return s.openSession(ctx, code)
error is just an interface:
type error interface {
Error() string
}
Sentinel errors are package-level values callers can compare against:
var (
ErrNotFound = errors.New("not found")
ErrNicknameTaken = errors.New("nickname already taken")
)
Wrap with %w to add context while keeping the original reachable:
Die wichtigste Fähigkeit in der IT ist Kommunikation, die größte Herausforderung in der Softwarearchitektur sind Soft Skills — wie sollen LLMs genau diese Probleme lösen? Der Vortrag geht der Frage nach, ob KI Softwarearchitekt:innen ersetzen kann. Ausgangspunkt ist ein KI-gestütztes Architektur-Review mit einem sachlich falschen Ergebnis. Daraus folgen zwei Fragen: Lassen sich Menschen zu leicht von KI überzeugen? und Können LLMs zuverlässig sein?
Last updated: 2026-09-25Don’t communicate by sharing memory; share memory by communicating.
| Tool | What it is |
|---|---|
go f() |
Starts a goroutine: a function running concurrently, with a few KB of stack. Thousands are cheap. |
chan T |
Typed pipe between goroutines. Unbuffered = handoff, buffered = queue. |
select |
Wait on several channel operations at once; default makes it non-blocking. |
context.Context |
Carries cancellation, deadlines and request-scoped values down the call tree. |
sync.Mutex / RWMutex |
Plain locks, for when shared state is simpler than channels. |
sync.WaitGroup |
Wait for a group of goroutines to finish. |
sync.Once / OnceValue |
Lazy one-time initialisation. |
errgroup (golang.org/x/sync) |
WaitGroup + first error + cancel the rest. |
ch := make(chan int)
go func() { ch <- compute(1) }()
result := <-ch
One channel per subscriber, and a non-blocking send so a slow client can never stall the publisher.
Last updated: 2026-10-04Since Go 1.22, net/http.ServeMux handles methods and path parameters, so a typical JSON API needs no router library. Frameworks like gin or chi still work, but are no longer required.
ServeMuxmux := http.NewServeMux()
mux.HandleFunc("GET /healthz", health)
mux.HandleFunc("POST /api/join", join)
mux.HandleFunc("GET /api/quizzes/{id}", getQuiz)
mux.HandleFunc("PUT /api/quizzes/{id}", updateQuiz)
mux.HandleFunc("GET /files/{path...}", serveFile) // wildcard rest-of-path
mux.Handle("/", spaHandler) // catch-all
func getQuiz(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
...
}
405 with an Allow header.panic: pattern "GET /" conflicts with pattern "/api/":
GET / matches fewer methods than /api/, but has a more general path pattern
No framework concept needed: a middleware takes a handler and returns a handler.
Last updated: 2026-10-04One binary ships with the language and covers dependency management, building, testing, formatting and static checks. ./... means “this package and everything below it”.
go command| Task | Command |
|---|---|
| Create a project | go mod init example.com/app |
| Add a dependency | go get github.com/x/y@v1.4.0 |
| Sync deps with imports | go mod tidy |
| Why is a dep here? | go mod why -m github.com/x/y |
| Run without building | go run ./cmd/server |
| Build a binary | go build -o bin/app ./cmd/server |
| Install a CLI tool | go install github.com/air-verse/air@latest |
| Pin a dev tool in the project | go get -tool ... → go tool ... |
| Test | go test ./... |
| Format | gofmt -w . / go fmt ./... |
| Static checks | go vet ./... |
| Docs in the terminal | go doc net/http.ServeMux |
| Multi-module workspace | go work init ./svc-a ./svc-b |
| Generate code | go generate ./... |
go.mod declares the module path, Go version and direct dependencies; go.sum pins checksums. Commit both.go line is the minimum language version; a toolchain line can require a newer toolchain, which is downloaded automatically.github.com/jackc/pgx/v5.# Cross-compile: just set two env vars
GOOS=linux GOARCH=arm64 go build -o bin/app-linux-arm64 ./cmd/server
# Static, reproducible, stripped, with an injected version
CGO_ENABLED=0 go build -trimpath \
-ldflags="-s -w -X main.version=1.4.2" -o bin/app ./cmd/server
| Flag | Effect |
|---|---|
CGO_ENABLED=0 |
No libc dependency → fully static, runs on scratch / distroless |
-trimpath |
Removes local file paths from the binary |
-ldflags="-s -w" |
Strips symbol table and DWARF (smaller binary) |
-X pkg.var=value |
Sets a string variable at link time |
go version -m bin/app shows the module versions and build settings baked into any Go binary.
No framework required: tests are plain functions in _test.go files next to the code, run by go test.
| Need | Go |
|---|---|
| A test | func TestSave(t *testing.T) in store_test.go |
| Assertions | if got != want { t.Errorf(...) } (or testify, if you like) |
| Parameterized tests | Table-driven test + t.Run subtests |
| Test doubles | Hand-written fake of a small, consumer-side interface |
| HTTP servers/clients in tests | net/http/httptest |
| Per-test setup / teardown | Helper function + t.Cleanup |
| Per-package setup / teardown | TestMain(m *testing.M) |
| Temp files, env vars | t.TempDir(), t.Setenv() |
| Separating slow suites | Build tags (//go:build integration) |
| Real dependencies in Docker | testcontainers-go |
| Browser tests | playwright-go |
| Coverage, parallelism | go test -cover, t.Parallel() |
Package choice: package foo tests internals; package foo_test (black-box) only sees the exported API.
One name, one signature. Use an options struct or functional options:
type Option func(*Server)
func WithTimeout(d time.Duration) Option { return func(s *Server) { s.timeout = d } }
func NewServer(addr string, opts ...Option) *Server {
s := &Server{addr: addr, timeout: 30 * time.Second}
for _, o := range opts {
o(s)
}
return s
}
srv := NewServer(":8080", WithTimeout(5*time.Second))
func find() error {
var e *MyError // nil
return e // interface holding (*MyError, nil)
}
find() == nil // false (!)
An interface is (type, value); it’s only nil when both are. Return a literal nil on success.
Two generators run in sequence against a single OpenAPI spec:
| Generator | Owns | Output |
|---|---|---|
oapi-codegen |
Types, enums, nested structs, optional pointers | generated/types.go |
gen/main.go |
Per-tag interfaces, route registration, strict glue | generated/interfaces.go, routes.go, routes.go, requests.go |
The result is a clean boundary: generated code you never touch, and handler files you fully own.
make install-tools
Installs oapi-codegen into $(go env GOPATH)/bin.
spec/
api.yaml ← source of truth, edit this
cfg/
types.yaml ← oapi-codegen config (types only)
gen/
main.go ← custom splitter — generates per-tag interfaces + routes
generated/ ← never edit, safe to delete and rebuild
types.go
interfaces.go
routes.go
requests.go ← strict mode only
server/
middleware/ ← hand-written
handlers/ ← hand-written, implement generated interfaces
server.go ← wires router + middleware + handlers
Makefile
Handlers receive *gin.Context directly. Full manual control.
Why architecture docs fail:
| Problem | Why it happens |
|---|---|
| Too much too early | Spec written before code — outdated by first sprint |
| No time during development | Devs skip it under pressure, never catch up |
| No ownership | Nobody is explicitly responsible → everyone assumes someone else does it |
| Unclear scope | “What actually goes in there?” — no shared answer |
Solution framing: Don’t ask “how do we document everything?” — ask “what and how do we document?” Less is more. Document decisions and context, not implementation details the code already tells you.
Last updated: 2026-05-05Website: https://www.esdm.io/
Core idea: Domain-first modeling language (YAML) that captures DDD, CQRS, and Event Sourcing building blocks in a machine- and human-readable format.
Talk about domain first — technology is secondary.
Common mistake: teams jump to “we’ll use Kafka / PostgreSQL / microservices” before defining what the system actually does. ESDM flips this:
This makes conversations with domain experts productive — they don’t need to understand Kafka to participate.
Last updated: 2026-05-05Balance: Not too much, not too little. Every metric and log must answer a real operational question.
Use Icinga (as already deployed in your infrastructure) and focus on:
Question every metric: If this threshold fires, can we do something about it in 30 seconds? If not, it’s noise.
Last updated: 2026-05-05Rollout date: 18. Mai
Spring releases a batch of security fixes discovered via the Mythos vulnerability check. All branches up to Spring 2.7 are affected.
| Term | Meaning | Examples |
|---|---|---|
| Extensional | Represents external reality — maps things from outside the domain | DTOs, API models, database rows |
| Intensional | Expresses domain concepts from within — gives things meaning | Value Objects, Entities, Aggregates |
The goal: code that reads like the domain language, not like database schema.
Last updated: 2026-05-05