Faisalaffan Blog
|
🇬🇧
Faisalaffan Blog
Career14 min read

What I Did as a Senior Engineer — Architecture, Trade-Offs & Technical Judgment

I stopped being evaluated on lines of code. I was evaluated on decisions — what architecture to use, what trade-offs to accept, and how to guide other developers. This is the real story of designing IoT telemetry for 50K events/sec, microservices for Australia's largest insurer, and the stack decision that became my most-read article.

Faisal Affan
Faisal Affan
Published
What I Did as a Senior Engineer — Architecture, Trade-Offs & Technical Judgment

What I Did as a Senior Engineer

"You don't become a senior engineer by writing more code. You become one by making decisions that outlive you." — Me, after breaking production for the 47th time

The jump from mid-level to senior is the most disorienting transition in engineering. Your mid-level years taught you to ship features. Now, nobody cares how fast you personally ship. They care whether the architecture you chose survives three years of growth, whether the trade-offs you documented save the next team six months of rediscovery, and whether the junior engineers you mentored can ship without you.


The Senior Proposition

What Changed

Before (Mid-Level): "Can you own a feature end-to-end and ship it reliably?" Now (Senior): "Can you make architectural decisions that multiple teams will follow — and be right about the trade-offs 80% of the time?"

I stopped being evaluated on lines of code. I was evaluated on decisions — what architecture to use, what trade-offs to accept, and how to guide other developers without writing their code for them.

Here is what that actually looked like in practice.


Case Study 1: Petrosea Minerva — IoT Fleet Telemetry

Role: Backend Engineer & System Architect Stack: Go, MQTT, Kafka, TimescaleDB, React Scale: 50K telemetry events/second, 100+ heavy equipment units across 3 remote mining sites

Petrosea's mining operations had a problem: 100+ dump trucks, excavators, and support vehicles operating across remote sites in Kalimantan generated continuous telemetry — GPS position, engine temperature, fuel consumption, payload weight — every 2 seconds. The data was critical for real-time dispatch decisions, geofence alerts, and predictive maintenance. But the sites had intermittent satellite connectivity that made HTTP-based architectures unreliable.

The Architecture Decision

I designed a real-time geofencing engine capable of processing 50K events per second with sub-2-second alert latency. The core architectural choice was the data pipeline:

Architecture: Edge-to-Cloud IoT Pipeline

Problem: 100+ mining trucks across remote sites generating GPS/engine data every 2 seconds. Intermittent satellite connectivity — sometimes 6-8 hours of complete blackout. Data must reach HQ dashboard within 5 seconds for real-time geofence alerts.

Architecture Decision:

Plaintext
Edge Gateway (Go) → MQTT (QoS 1) → Kafka → Stream Processor → TimescaleDB → WebSocket → React Dashboard
                                                                    ↓
                                                              Geofence Engine (< 2s alerts)

Component Breakdown:

  1. Edge Gateway (Go): Runs on Raspberry Pi-class hardware mounted on each truck. Local SQLite buffer for offline periods. Publishes telemetry via MQTT when connectivity is available. Handles protocol translation from various OEM sensors (Caterpillar, Komatsu, Hitachi) into a normalized schema.

  2. MQTT Broker (Mosquitto): Chosen explicitly for its lightweight protocol, QoS levels (QoS 1 for at-least-once delivery), and persistent sessions that survive disconnection. When a truck emerges from a blank spot after 6 hours, MQTT's session state re-establishes and the backlog drains without developer intervention.

  3. Kafka Stream Processor: Decouples ingestion from processing. Handles the "thundering herd" problem when 50 trucks reconnect simultaneously — Kafka buffers the burst while downstream consumers process at their own pace.

  4. TimescaleDB: Time-series optimized PostgreSQL extension. Chosen over InfluxDB because we needed SQL JOINs between telemetry and equipment metadata. 10x query performance improvement over vanilla PostgreSQL for time-range queries.

  5. Geofence Engine: In-memory spatial index using R-tree. Processes incoming GPS coordinates against predefined geofence polygons. Sub-second detection triggered alerts via WebSocket push — no polling required.

Key Trade-Off: MQTT over HTTP. HTTP polling at 50K events/sec would have required 50 requests per second minimum, each with TLS handshake overhead. On satellite links with 600ms+ latency, the retry storm alone would have collapsed the pipeline. MQTT's persistent TCP connection with QoS 1 and session state eliminated this entirely.

What I'd Change: Add a dead-letter queue for unprocessable messages and implement backpressure signaling from Kafka to MQTT. We lost ~0.3% of telemetry in the first month from Kafka consumer lag during peak hours — a backpressure mechanism would have prevented this.

Impact

Metric Before After
Telemetry processing Manual/log-based Real-time, 50K events/sec
Geofence alert latency 5-10 minutes < 2 seconds
Fuel theft detection Unknown (undetected) Caught in month one
Annual savings — Rp 3.2B ($200K)
Fleet utilization visibility Paper-based shift reports Live dashboard, 65% → 79%

Full case study →


Case Study 2: IAG Cybera360 — Insurance Platform Architecture

Role: Frontend & Backend Engineer Stack: Nuxt.js (SSR), Go microservices, PostgreSQL, D3.js Scale: 5+ partner integrations, digitized entire insurance lifecycle for Australia's largest insurer

IAG (Insurance Australia Group) — Australia's largest general insurer, ~AU$12B market cap — needed a platform to serve the underserved SME cyber insurance market. The existing process took 3-4 days for a quote, involved 100+ manual questions, and required underwriter back-and-forth via email. We had to digitize the entire lifecycle: risk assessment → quoting → binding → monitoring → claims.

The Critical Trade-Off

Architecture Decision: SSR vs SPA for Insurance Quoting

Business Requirement: Australian insurance brokers search for "cyber insurance quote" on Google. If our quoting flow isn't indexable by search engines, brokers won't find us — and they'll go to competitors.

Decision: Nuxt.js Server-Side Rendering (SSR) over a React Single Page Application (SPA).

Why Not React SPA:

  • Search engine crawlers (Googlebot, Bingbot) execute JavaScript inconsistently for deep-linked content
  • Insurance quoting URLs are dynamically generated — /quote/business-profile?industry=tech&employees=50 — each URL needs to be indexable with unique meta tags
  • Brokers share quoting links via email — Open Graph previews required server-rendered meta tags

Why Nuxt.js SSR:

  • Full server-side rendering for every route — Google sees complete HTML on first request
  • Vue.js development model with Nuxt's module ecosystem (auth, sitemap, PWA)
  • The team had stronger Vue.js experience than Next.js at the time — pragmatic choice over dogmatic

Architecture Overview:

Architecture Diagram
Browser → Nuxt.js SSR → Go API Gateway → Microservices:
                                           ├── Risk Assessment Service (vulnerability scanning integration)
                                           ├── Quoting Engine (premium calculation + rules engine)
                                           ├── Policy Management Service (binding + document generation)
                                           ├── Partner Integration Layer (UpGuard, Acronis, RHCS)
                                           └── Notification Service (email, dashboard alerts)

Trade-Off Accepted: SSR introduces higher server cost per request compared to a static SPA. Each page render requires a Node.js server round-trip. We mitigated this with Redis page caching for public pages (rate pages, FAQ, blog) and reserved dynamic SSR only for authenticated quoting flows.

Result: The platform was fully indexed by Google within 2 weeks of launch. Organic traffic from broker searches contributed 40% of new quoting sessions in the first quarter. The digitized quoting flow reduced time-to-quote from 3-4 days to under 15 minutes.

Impact

Metric Before After
Quote turnaround 3-4 days < 15 minutes
Insurance lifecycle Manual/paper Fully digitized
Partner integrations None 5+ (UpGuard, Acronis, RHCS, etc.)
Platform reach Australia only Asia Pacific (Singapore, Malaysia, Hong Kong)

Full case study →


Case Study 3: Why Golang — The Stack Decision

Role: Technology Decision-Maker Stack: Go (chosen), evaluated against Node.js, Python, Rust

Every senior engineer eventually faces the question: "What's your primary stack and why?" This isn't a trivia question — it's a test of your ability to evaluate trade-offs across multiple dimensions: performance, developer productivity, ecosystem maturity, hiring market, and long-term maintenance cost.

I evaluated four options against five criteria:

Criterion Go Node.js Python Rust
Concurrency model Goroutines (built-in) Event loop (single-threaded) GIL-limited Async/await (complex)
Compilation speed < 2 seconds N/A (interpreted) N/A (interpreted) 30-120 seconds
Deployment Single binary Node + node_modules Python runtime + pip Single binary
Learning curve Low (25 keywords) Low Low Steep (ownership, lifetimes)
Hiring in Indonesia Growing rapidly Mature Mature Niche

The Decision: Go won on simplicity, compilation speed, and deployment ergonomics — not raw performance. A Rust advocate could correctly point out that Rust outperforms Go in CPU-bound workloads by 2-5x. But raw CPU throughput was rarely my bottleneck. Network I/O, database queries, and message queue throughput were. Go's goroutine-based concurrency model handles those perfectly.

Full article →

The Recurring Pattern

Every senior-level decision in my career followed the same pattern: identify the real constraint (not the hypothetical one), evaluate trade-offs explicitly, and choose the option that optimizes for the bottleneck — whether that's developer productivity, deployment simplicity, or SEO requirements.


Architecture Diagrams I Designed

Beyond individual projects, the senior role demanded that I document architecture decisions so they could be understood, challenged, and extended by other engineers. Here are two key diagrams that represent the most complex architectural problems I solved.

IoT Telemetry Architecture (Petrosea Minerva)

Problem Statement: 100+ mining trucks generating 50K telemetry events/second across 3 remote sites with intermittent satellite connectivity. Data must reach HQ dashboards in under 5 seconds for real-time dispatch and safety alerts.

Architecture:

Architecture Diagram
┌─────────────────┐     ┌──────────┐     ┌──────────┐     ┌───────────────┐
│  Edge Gateway   │────▶│   MQTT   │────▶│  Kafka   │────▶│  TimescaleDB  │
│  (Go + SQLite)  │     │  Broker  │     │  Stream  │     │  (Time-Series)│
│  Local Buffer   │     │  QoS 1   │     │  Cluster │     │               │
└─────────────────┘     └──────────┘     └──────────┘     └───────┬───────┘
      │                                                           │
      │ Offline: Local SQLite                                     │
      │ Online: MQTT publish                                      │
      │  (QoS 1, persistent session)                              │
                                                                  │
┌────────────────┐     ┌──────────────────┐     ┌─────────────────┐│
│  Geofence      │◀────│  Stream Processor│◀────│  Kafka Consumer ││
│  Engine (R-Tree)│     │  (Go)           │     │  (Go)           ││
│  < 2s alerts   │     │  + Alerting     │     └─────────────────┘│
└────────────────┘     └──────────────────┘                        │
                                                                   │
                                                      ┌────────────▼───────┐
                                                      │  WebSocket Server  │
                                                      │  (Go)              │
                                                      └────────────┬───────┘
                                                                   │
                                                      ┌────────────▼───────┐
                                                      │  React Dashboard   │
                                                      │  (Real-Time Maps)  │
                                                      └────────────────────┘

Decision: Edge gateway with local SQLite buffer → MQTT publish (QoS 1) → Kafka stream processing → TimescaleDB → WebSocket push to React.

Trade-Offs Considered:

  • HTTP polling (rejected: 50K events/sec × HTTP request overhead would saturate satellite links)
  • gRPC streaming (rejected: satellite link flapping causes gRPC connection resets every 5-15 minutes)
  • MQTT chosen for: 3x less bandwidth than HTTP, built-in QoS levels, persistent session state automatic reconnection, and pub/sub decoupling
Offline-First Sync Engine (Tepati)

Problem Statement: 5,000+ field agents working in areas with zero internet connectivity. They fill dynamic forms, capture photos, and collect digital signatures. All data must sync without loss when connectivity returns. Previous Microsoft Power Apps solution had 23% data loss rate.

Architecture:

Architecture Diagram
┌──────────────────┐     ┌──────────┐     ┌──────────────────┐
│  Flutter Mobile  │────▶│   MQTT   │────▶│  Go API Gateway  │
│  App             │     │  Broker  │     │                  │
│  ┌────────────┐  │     │  QoS 1   │     │  ┌────────────┐  │
│  │ SQLite DB  │  │     └──────────┘     │  │ CRDT       │  │
│  │ (Local)    │  │                      │  │ Resolution │  │
│  ├────────────┤  │     ┌──────────┐     │  ├────────────┤  │
│  │ Change Log │  │────▶│   MQTT   │────▶│  │ PostgreSQL │  │
│  │ Table      │  │     │  Batch   │     │  │ (Source of │  │
│  └────────────┘  │     │  Upload  │     │  │  Truth)    │  │
└──────────────────┘     └──────────┘     └──────────────────┘
     │
     │ Offline: Write to SQLite + change log
     │ Online: Push change log via MQTT
     │ Reconnect: Pull server changes, apply CRDT merge

Decision: SQLite local DB → change log table → MQTT push when online → server-side conflict resolution (last-write-wins with timestamp + device ID vector).

Trade-Offs Considered:

  • CRDT-based approach chosen for: automatic conflict resolution without central coordination, single master architecture with offline writes, low complexity for form-based (non-collaborative) data
  • Operational Transform rejected: overkill for this use case — no collaborative editing needed, higher complexity for conflict resolution, CRDT's eventual consistency was sufficient
  • Custom sync protocol over MQTT rejected in favor of change log pattern: change logs provide audit trail and replay capability, incremental sync (only send deltas), and deterministic conflict resolution

Result: 0% data loss (from 23% in the previous system). 4.1M end-users served.


My Senior Scorecard

Portfolio Item Did I Do It? Evidence
System architecture diagrams ✅ Documented across multiple case studies with trade-off analysis
Trade-off decisions with rationale ✅ Go vs Rust, MQTT vs HTTP, SSR vs SPA — each with explicit evaluation matrix
Mentorship impact ⚠️ Informal mentoring only — never created a structured program
Speaking/writing ✅ 20+ published technical articles including most-read Go stack analysis
Cross-team architecture influence ✅ Designed patterns adopted by 3+ product teams (form engine, telemetry pipeline)
Production incident leadership ✅ Led RCA for critical IoT incidents, documented runbooks for ops handover
ADR / technical documentation ⚠️ Inconsistent — decisions documented in case studies but not formal ADRs in every case

The informal mentorship column is my biggest gap. I guided juniors and mid-level engineers on their code reviews and architecture decisions, but I never created a structured mentorship framework. Looking back, that was a missed opportunity — the engineers I informally mentored grew faster, but the organization couldn't replicate the pattern because it wasn't documented.


Advice for Engineers Transitioning to Senior

Document Every Architecture Decision — Even the Wrong Ones

The difference between a senior and a mid-level isn't making perfect decisions — it's being able to articulate why you made a decision, what alternatives you considered, and when you'd revisit it. Start writing ADRs (Architecture Decision Records) for every non-trivial technical choice. The Go-stack analysis I published became my most-read article not because it was technically impressive, but because it showed the evaluation matrix — the exact same format you need for your senior portfolio.

Mentoring Is Not Optional

You don't become a senior by knowing more. You become one by making others more effective. Start with one junior engineer. Code review with explanation ("I'd do X because Y, but your approach works if Z"). Pair program on a complex feature. Write internal documentation that unblocks the team. The compound interest of mentoring pays off faster than any individual feature you could build.

Write Publicly — Your Engineering Portfolio Compounds

Every architecture decision, trade-off analysis, or production postmortem you write is an asset. It validates your thinking for future employers, helps other engineers facing the same problems, and builds recognition that makes senior roles easier to secure. I wrote 20+ technical articles during my senior years — and every single one of them contributed more to my career trajectory than any single feature I shipped.

Measure Everything, Optimize What Matters

Mid-level engineers optimize for "does it work." Senior engineers optimize for "does it survive." Latency, throughput, error budgets, MTTR, deployment frequency — these aren't DevOps metrics. They're senior engineering metrics. Start measuring before you optimize. The "$200K annual savings" from Petrosea exists because I instrumented the system from day one and could point to specific improvements with specific dollar values.


What I'd Do Differently

  • Formalize mentorship earlier. The junior and mid-level engineers I guided informally are the ones I'm most proud of. But I should have created a repeatable pattern — tech talks, pair programming schedules, growth checklists — that outlived my presence.
  • Write formal ADRs for every major decision. I documented trade-offs in case studies but not in the machine-readable ADR format that organizations need for institutional memory.
  • Say "no" more clearly to non-senior work. The hardest transition was learning that my job wasn't to write more code. Every hour I spent building a feature was an hour I didn't spend reviewing architecture, unblocking a teammate, or documenting a decision. The seniors who excel are ruthlessly strategic about where they invest their time.

Senior engineering is not the ceiling. It's the launching pad. The architecture decisions you make at this level outlive your tenure. The engineers you mentor outgrow your influence. The trade-offs you document save future teams years of rediscovery. Your code compiles and deploys. But your decisions compound — and that's what makes a senior engineer.

Related stories

Career
Career
Every Role I've Played — Junior to CTO, What I Actually Did at Each Level

Every Role I've Played — Junior to CTO, What I Actually Did at Each Level

A candid career retrospective tracing my journey through every engineering role: Junior developer grinding through fundamentals, Mid-level shipping production features, Senior designing architectures, Staff building org-wide leverage, and CTO-level strategic decisions. Each section maps to the portfolio strategy framework with real projects, real metrics, and real lessons.
Faisal Affan
Faisal Affan
Published
Career
What I Did at the CTO Level — Business Strategy Through Technology

What I Did at the CTO Level — Business Strategy Through Technology

At this level, your portfolio isn't about technology — it's about how technology created business value. I reduced AI inference costs by 90%, built an MCP orchestration that 3x'd developer output, and established a FOSS strategy where every repo is a 24/7 portfolio asset. This is what engineering leadership actually delivers.
Faisal Affan
Faisal Affan
Published
Career
What I Did as a Junior Developer — The Fundamentals That Shaped Everything

What I Did as a Junior Developer — The Fundamentals That Shaped Everything

No production experience. GitHub full of tutorial repos. One asset: the ability to learn fast and zero fear of breaking things. This is what I actually built as a junior engineer — WordPress sites, a PHP university system, and the hardcoded credentials mistake that taught me security forever.
Faisal Affan
Faisal Affan
Published
Career
What I Did as a Mid-Level Engineer — Shipping, Ownership & Production Scars

What I Did as a Mid-Level Engineer — Shipping, Ownership & Production Scars

I shipped real features to real users. I broke production and fixed it alone at 2 AM. I built a mobile banking app with 680K downloads, a mining fleet system saving $200K/year, and a field operations platform for 4.1M users. This is what mid-level engineering actually looks like — the projects, the metrics, and the production incidents that made me a real engineer.
Faisal Affan
Faisal Affan
Published