Part 2: Planning, Architecture, and Project Setup

Introduction

The most expensive code is the code you have to rewrite. In my early projects, I'd jump straight into coding—only to refactor everything weeks later when requirements evolved. Now, I spend 20% of project time on planning, which saves me 80% of headaches later.

This part covers my battle-tested approach to starting projects right.

Planning & Architecture

The Real-World Problem

Let's work through a concrete example I built last year: a webhook processing service for handling Stripe payment events.

Requirements:

  • Receive webhooks from Stripe

  • Verify signatures

  • Process events asynchronously

  • Retry failed processing

  • Monitor success/failure rates

  • Handle 1000+ events/hour

My Architecture Process

spinner

Step 1: Component Identification

I start with a simple component diagram:

spinner

Why this architecture?

  • API decoupled from processing: Stripe gets fast 200 response

  • Queue for reliability: If worker fails, event isn't lost

  • Redis cache: Prevent duplicate processing

  • Separate monitoring: See failures before customers do

Step 2: Define Boundaries

For my webhook service, I defined clear boundaries:

spinner

Lesson learned: When I tried making the webhook handler do everything, it became impossible to test and deploy. Separation saved me.

Step 3: Choose Tech Stack

My decision matrix for the webhook service:

Requirement
Options Considered
Choice
Reason

Language

Python, Node.js, Go

Node.js

Team expertise, async I/O

Framework

Express, Fastify, Nest.js

Fastify

Performance, built-in validation

Queue

RabbitMQ, SQS, Redis

Redis

Already using, simple setup

Database

PostgreSQL, MongoDB

PostgreSQL

ACID compliance needed

Deployment

ECS, Lambda, K8s

ECS

Right balance of simplicity/control

Anti-pattern I avoided: Using the "hottest" tech. Kubernetes was overkill for this service. ECS was perfect.

Step 4: Design Data Flow

Here's the actual flow from my implementation:

spinner

Critical decision: Transaction boundary includes webhook log but NOT email. Why? Email failure shouldn't rollback payment update.

Step 5: Plan for Failure

I use a failure modes table:

What Can Fail
Impact
Detection
Mitigation

Stripe signature invalid

Medium

Immediate

Log, alert, return 401

Redis down

High

Health check

Fallback to direct processing

Worker crash

Low

Process monitor

Restart, event stays in queue

Database deadlock

Medium

Metrics

Retry with exponential backoff

Duplicate event

Low

Redis cache

Idempotency check

Real incident: Redis went down during Black Friday. Because I planned for it, we fell back to direct processing. No lost events.

Step 6: Document Decisions

I write Architecture Decision Records (ADRs):

Why ADRs saved me: Six months later, new team member asked "why not use SQS?" I just linked the ADR.

Project Setup & Scaffolding

The Scaffolding Strategy

I maintain templates for common project types. Here's my Node.js TypeScript service template:

Automated Setup

Instead of manually creating files, I use my initialization script:

Usage:

Time saved: What used to take 2 hours (copying files, updating configs) now takes 30 seconds.

Environment Configuration

I use a .env.example file that documents all required variables:

Validation on startup:

Why this matters: App crashes immediately with clear error if config is wrong. No mysterious production failures.

Git & Commands

My Git Workflow

spinner

Essential Git Commands I Use Daily

1. Clean Commit History

2. Cherry-Pick Specific Fixes

Real scenario: Production bug in payment validation. I cherry-picked just the fix commit without pulling in work-in-progress features.

3. Find When Bug Was Introduced

Saved me 4 hours tracking down a regression in JWT validation.

4. Stash Work-in-Progress

5. Rewrite History (Carefully!)

Lesson learned: I committed an API key once. This saved me from rotating 50+ client secrets.

Branch Strategy

For my webhook service:

spinner

Branch naming convention:

  • feature/short-description - New functionality

  • bugfix/short-description - Bug fixes

  • hotfix/short-description - Production emergencies

  • chore/short-description - Maintenance (deps, docs)

Commit Message Format

I follow conventional commits:

Examples from my webhook service:

Why this format?

  • Auto-generate changelogs

  • Filter commits by type

  • Clear communication with team

Git Hooks for Code Quality

I use Husky for git hooks:

What this prevents:

  • ❌ Committing code that doesn't lint

  • ❌ Committing code that doesn't type-check

  • ❌ Committing code that breaks tests

  • ❌ Committing with bad commit messages

Trade-off: Commits take 10-30 seconds longer, but saves hours in code review and debugging.

Practical Git Troubleshooting

Scenario 1: Accidentally Committed to Main

Scenario 2: Need to Undo Public Commit

Scenario 3: Merge Conflict

My conflict resolution workflow:

Keep both approaches, combine them logically.

Development Environment Setup

My Standard Dev Container

I use Docker Compose for consistent environments:

Start everything:

Why this approach?

  • ✅ Same environment for all developers

  • ✅ No "works on my machine" issues

  • ✅ Easy onboarding for new team members

  • ✅ Matches production architecture

VS Code Configuration

My .vscode/settings.json:

Debugging Setup

My .vscode/launch.json:

Usage: Press F5 in VS Code, set breakpoints, step through code.

Time saved: Beats console.log() debugging by miles.

Complete Setup Flow

Here's my actual workflow starting a new service:

spinner

Timeline (webhook service):

  • Planning: 2 hours (architecture, ADRs)

  • Scaffolding: 5 minutes (automated script)

  • Git setup: 10 minutes (repo, hooks, CI)

  • Dev env: 15 minutes (Docker Compose)

  • First test: 20 minutes (test framework setup)

Total: ~3 hours to writing first line of business logic

Compare to my early days: Would spend 2-3 days just setting up tooling.

Key Takeaways

  1. Plan before coding: 20% planning time saves 80% refactoring time

  2. Automate scaffolding: Don't manually create project structure

  3. Document decisions: ADRs prevent repeated discussions

  4. Clean Git history: Makes code review and debugging easier

  5. Consistent environment: Docker Compose eliminates "works on my machine"

What's Next

In Part 3, we'll actually write code:

  • Development workflow

  • Testing strategies

  • Debugging techniques

  • Browser automation

We'll implement the webhook handler with proper error handling, tests, and observability.


Ready to write some code? Part 3 is where theory meets practice.

Last updated