DEV Community

Cover image for CountIQ: A Systems Notebook on Real-World Backend Engineering
Zack Grooms
Zack Grooms

Posted on

CountIQ: A Systems Notebook on Real-World Backend Engineering

This is a developer notebook documenting what building a real backend is teaching me about invariants, state transitions, concurrency, and system design. No fluff — just real architecture lessons from building CountIQ.

I didn’t build CountIQ to ship a product.
I built it to understand what actually happens inside a backend.

Not in theory.
In reality — with state, invariants, race conditions, and deployment in mind.

This post isn’t a tutorial.
It’s a developer notebook.

I’m documenting what building a real system is teaching me about:

  • modeling state instead of writing endpoints
  • aligning database constraints with domain rules
  • preventing race conditions through transaction boundaries
  • why most backend bugs are invariant bugs, not syntax bugs
  • and how a simple service evolves into something deployable

CountIQ started as a small inventory backend.
But it quickly became a controlled environment for learning system design the hard way — by building, breaking, and fixing a real system.

If you’re learning backend engineering and want to see how a system evolves layer by layer — architecture, logging, concurrency, and deployment — this series is for you.

No frameworks magic.
No hand-wavy abstractions.

Just the mental models and design decisions that actually make a backend stable.


1. Core System Model

CountIQ is modeled as a state machine.


S = Map[id → item]
S' = f(S, input)

Enter fullscreen mode Exit fullscreen mode

Translation

  • S → current system state
  • input → request
  • f → transition logic
  • S' → new system state

Every endpoint is a state transition.

If validation fails:


S' = S

Enter fullscreen mode Exit fullscreen mode

State must remain unchanged.


State Visualization


Current State (S)
{
"1": milk,
"2": eggs
}

Enter fullscreen mode Exit fullscreen mode
request
  ↓
Enter fullscreen mode Exit fullscreen mode

Apply transition f(S, input)

Enter fullscreen mode Exit fullscreen mode
Enter fullscreen mode Exit fullscreen mode

New State (S')
{
"1": milk,
"2": eggs,
"3": bread
}

Enter fullscreen mode Exit fullscreen mode

2. Architecture


interface
↓
transport
↓
service
↓
domain
↓
database

Enter fullscreen mode Exit fullscreen mode

Layer Responsibilities

Transport

  • parse request
  • no business logic

Service

  • orchestrate mutation
  • enforce boundaries

Domain

  • validate invariants
  • normalize input

Database

  • persistence
  • final authority

3. Mutation Pipeline

All mutations follow one path:


read → validate → commit

Enter fullscreen mode Exit fullscreen mode

Diagram


Request
↓
Read state
↓
Validate invariants
↓
Commit transaction
↓
Return response

Enter fullscreen mode Exit fullscreen mode

If validation fails:


no commit
state unchanged

Enter fullscreen mode Exit fullscreen mode

4. Bug Refurbishment

Global vs Per-Owner Uniqueness

Original constraint


name UNIQUE

Enter fullscreen mode Exit fullscreen mode

Observed behavior


User A → create milk → OK
User B → create milk → rejected

Enter fullscreen mode Exit fullscreen mode

System enforced:

name globally unique

This was incorrect.


Correct invariant


(owner_id, name) must be unique

Enter fullscreen mode Exit fullscreen mode

Meaning:

  • same user → duplicates forbidden
  • different users → duplicates allowed

Namespace Visualization


User A:
milk
eggs

User B:
milk   ← allowed
bread

Enter fullscreen mode Exit fullscreen mode

Fix Alignment

Domain

Define invariant clearly.

Database


UNIQUE(owner_id, name)

Enter fullscreen mode Exit fullscreen mode

Service

Attempt write → catch integrity error → translate.

Tests

Verify:

  • same owner duplicate fails
  • different owners succeed

5. Concurrency Model

Key truths:

  • processes don’t share memory
  • threads share heap
  • requests interleave
  • database enforces consistency

Concurrency Diagram


Request A ──────┐
├── Database
Request B ──────┘

Enter fullscreen mode Exit fullscreen mode

Both requests may attempt mutation simultaneously.

Database constraint prevents corruption.


6. Request Lifecycle Graph


client
↓
OS
↓
kernel
↓
network
↓
process
↓
database
↓
response

Enter fullscreen mode Exit fullscreen mode

Latency becomes:


path cost + contention

Enter fullscreen mode Exit fullscreen mode

7. Testing Philosophy

Tests validate the mental model.

Rules:

  • assert state unchanged on failure
  • test transitions
  • separate transport vs service tests
  • integration tests verify layers

If tests are hard to write:

the model is wrong.


8. Logging System Direction

Logging is a system component.

Goals:

  • structured logs
  • lifecycle visibility
  • transition tracing
  • concurrency debugging

Logging = observability = understanding.


9. Why This Project Exists

CountIQ is a systems lab for learning:

  • invariants
  • mutation safety
  • concurrency
  • architecture
  • deployment

Built:

  • layer by layer
  • frozen interfaces
  • deployment-first mindset

10. Forward Roadmap


logging middleware
user model
Docker
AWS deployment
Python internals
concurrency deep dive

Enter fullscreen mode Exit fullscreen mode

Each addition updates this notebook.


Developer Takeaway

Most backend bugs are not syntax bugs.

They are invariant bugs.

If the model is wrong:

  • code feels fragile
  • fixes are temporary

If the model is correct:

  • code simplifies
  • scaling becomes predictable

Rule:

Model the system first.

Then write the code.

Top comments (0)