DEV Community

Cover image for Doc-First Development: Because 'We'll Document It Later' Is a Lie
Mr. 0x1
Mr. 0x1

Posted on • Originally published at github.com

Doc-First Development: Because 'We'll Document It Later' Is a Lie

After 20+ years in software, I've seen every approach to building systems. This is the one that actually works.

Every time I mention "doc-first" or "spec-driven development" to engineers, I get blank stares.

"That sounds great in a perfect world."

Here's the thing: We're software engineers. We create worlds. Why wouldn't we try to make them perfect?

This articleβ€”and the complete codebase on GitHubβ€”exists to demonstrate that spec-first isn't some ivory tower ideal. It's practical. It's powerful. And with the tools we have today, it's easier than ever.


🎯 What We're Building

Synapse is a complete event-driven order processing system built entirely spec-first:

System Architecture

  1. Specifications are the source of truth β€” OpenAPI 3.1 for REST, AsyncAPI 3.0 for events
  2. Code is generated from specs β€” Types, interfaces, clients, event handlers
  3. Conformance tests validate everything β€” Implementation must match the specification
  4. Real infrastructure via Testcontainers β€” NATS, PostgreSQL, Redis spin up for tests

The result? A system where the contract is king, drift is impossible, and tests prove conformanceβ€”not just behavior.


πŸ“ The Doc-First Philosophy

Doc-First Lifecycle

Define Before You Build

Traditional development flows like this:

Write code β†’ Document later (maybe) β†’ Hope nothing drifts

Doc-first development flips the script:

Write spec β†’ Generate code β†’ Implement interfaces β†’ Prove conformance

Generated > Handwritten

When code is generated from specs:

  • βœ… No drift between documentation and implementation
  • βœ… Type safety guaranteed by the spec
  • βœ… Clients auto-generated for any language
  • βœ… Changes flow spec β†’ code, never backwards

Conformance Over Coverage

Traditional testing asks: "Does the code do what the code says?"

Conformance testing asks: "Does the code do what the **contract* says?"*

One tests implementation. The other tests promises.


πŸ”§ The Pipeline

Pipeline Stages

Orders flow through three Watermill-powered stages:

Stage Purpose
Validate Check required fields, verify amounts, validate customer
Enrich Customer tier lookup, fraud scoring, inventory check
Route Apply routing rules, determine destination, set priority

Each stage publishes to NATS, persists to PostgreSQL, and caches in Redis. Failed events go to a Dead Letter Queue for retry.


πŸ§ͺ Testing Strategy

Testing Strategy

OpenAPI Conformance

func TestOpenAPI_HealthEndpoint_ConformsToSpec(t *testing.T) {
    // Start real infrastructure with Testcontainers
    tc, _ := testutil.StartContainers(ctx, t, nil)

    // Create test suite from OpenAPI spec
    suite, _ := conformance.NewContractTestSuite(
        "openapi/openapi.yaml",
    )

    // Validate response matches spec schema
    result := suite.RunTest(ctx, client, baseURL,
        "GET", "/health",
        nil,
        http.StatusOK,
        "HealthResponse",  // Must match this schema
    )

    assert.True(t, result.Passed)
}
Enter fullscreen mode Exit fullscreen mode

AsyncAPI Conformance

func TestAsyncAPI_OrderPayload_ConformsToSpec(t *testing.T) {
    // Create validator from AsyncAPI spec
    suite, _ := conformance.NewEventContractTestSuite(
        "asyncapi/asyncapi.yaml",
    )

    // Validate event payload against schema
    result := suite.ValidateEvent(
        "orders/ingest",
        "OrderReceivedPayload",
        orderJSON,
    )

    assert.True(t, result.Passed)
}
Enter fullscreen mode Exit fullscreen mode

πŸ“ Project Structure

synapse-spec-first/
β”œβ”€β”€ asyncapi/              # AsyncAPI 3.0 event specs
β”œβ”€β”€ openapi/               # OpenAPI 3.1 REST specs
β”œβ”€β”€ cmd/
β”‚   β”œβ”€β”€ synapse/           # Application entry point
β”‚   └── synctl/            # Custom code generator
β”œβ”€β”€ internal/
β”‚   β”œβ”€β”€ generated/         # Generated from specs
β”‚   β”‚   β”œβ”€β”€ types.gen.go   # 31 domain types
β”‚   β”‚   β”œβ”€β”€ server.gen.go  # HTTP interface
β”‚   β”‚   β”œβ”€β”€ client.gen.go  # HTTP client
β”‚   β”‚   └── events.gen.go  # Event handlers
β”‚   β”œβ”€β”€ handler/           # HTTP handlers
β”‚   β”œβ”€β”€ pipeline/          # Watermill pipeline
β”‚   β”œβ”€β”€ conformance/       # Contract testing
β”‚   └── testutil/          # Testcontainers
└── scripts/               # Diagram generation
Enter fullscreen mode Exit fullscreen mode

πŸ”„ The Workflow

# 1. Edit the spec (the source of truth)
vim openapi/components/schemas/orders.yaml

# 2. Regenerate code
go run ./cmd/synctl

# 3. Implement the new interface methods
# (The compiler tells you what's missing)

# 4. Run conformance tests
go test ./internal/conformance/... -v
Enter fullscreen mode Exit fullscreen mode

That's it. Spec changes β†’ regenerate β†’ implement β†’ verify. The spec leads, the code follows.


πŸ™ Standing on Shoulders

This project wouldn't be possible without brilliant work from:

Specification Standards

Testing Infrastructure

  • Testcontainers β€” Real integration testing made accessible
  • Watermill β€” Elegant Go event-driven development

The Go Ecosystem

  • Chi β€” Lightweight routing done right
  • NATS β€” Derek Collison's gift to distributed systems

πŸ€” "But in the Real World..."

I've heard every objection:

"We don't have time to write specs first."

You don't have time to debug integration issues from undocumented API changes either. Pick your poison.

"Specs get out of date."

Not when they generate code. Not when conformance tests fail on drift.

"It's too much overhead."

The overhead is front-loaded. The payoff compounds forever.


πŸš€ Try It Yourself

# Clone the repo
git clone https://github.com/copyleftdev/synapse-spec-first.git
cd synapse-spec-first

# One-time setup
make setup

# Run all tests (including conformance)
make test

# Start the server
make run
Enter fullscreen mode Exit fullscreen mode

The Makefile Experience

We've included a comprehensive Makefile because developer experience matters:

make help              # See all available commands
make generate          # Regenerate code from specs
make test-conformance  # Run contract tests
make test-pipeline     # Run integration tests
make diagrams          # Generate architecture diagrams
make dev               # generate β†’ test β†’ run (full cycle)
Enter fullscreen mode Exit fullscreen mode
Workflow Command
First time setup make setup
After spec changes make generate
Quick validation make test-short
Full test suite make test
CI pipeline make ci

GitHub logo copyleftdev / synapse-spec-first

Doc-First Event Processing: A demonstration of specification-driven development with Go, OpenAPI, AsyncAPI, and Testcontainers

Synapse Mascot

Synapse: Doc-First Event Processing

"We're software engineers. We create worlds. Why wouldn't we try to make them perfect?"

A complete demonstration of specification-driven development using Go, showcasing how to build event-driven systems where the spec is the single source of truth.

Architecture

What Is This?

This project demonstrates a doc-first development workflow:

  1. Write specifications first (OpenAPI 3.1 + AsyncAPI 3.0)
  2. Generate code from specs (custom synctl generator)
  3. Implement to interfaces (handlers, pipeline stages)
  4. Validate with conformance tests (responses must match specs)

Quick Start

# Clone the repo
git clone https://github.com/copyleftdev/synapse-spec-first.git
cd synapse-spec-first

# One-time setup (downloads deps + generates code)
make setup

# Run all tests
make test

# Start the server
make run
Enter fullscreen mode Exit fullscreen mode

Makefile Commands

This project includes a comprehensive Makefile for a pleasant developer experience:

make help              # Show all available targets
Enter fullscreen mode Exit fullscreen mode

πŸš€ Quick Commands


















Command Description
make setup One-time setup for new clones
make generate Regenerate code
…






πŸ’ͺ A Challenge

If you've never tried spec-first development, I challenge you:

Build your next API starting with OpenAPI.

  1. Write the spec first
  2. Generate your types
  3. Implement to the interface
  4. Write conformance tests

Then tell me it's not worth it.


🌟 Final Thoughts

Philosophy

Someone once told me, "In a perfect world, that would be great."

We're software engineers. We create worlds.

Why wouldn't we try to make them perfect?


This project is a living demonstration. Fork it. Learn from it. Improve it. And maybe, just maybe, next time someone mentions doc-first development, there will be one fewer blank stare.

20 years of testing taught me this: Quality isn't an afterthought. It's the architecture.

Now go build something beautiful.


πŸ“š Resources

Top comments (0)