Backend engineer, Lagos

Ayomide Ilesanmi

Backend engineer. I design systems that stay up and stay understandable.

5+ years  ·  NestJS, Go, .NET, Java · distributed systems, billing, search

ayomide@tcg ~ zshtype help
$ whoami
Ayomide Ilesanmi (TheCodeGhinux)
Backend engineer in Lagos, Nigeria. 5+ years.
I work on distributed systems, billing, search and licensing, mostly on NestJS, with Go, Python and Java alongside.
Try: experience, cases, open architecture
Type a command below, or tap a suggestion.

Press Ctrl K for the command palettePress ? for shortcutsTab completes, ↑ ↓ history

Career as a trace

Each bar is a span. Select one to see what I built there and with what.

service / spantime

Doxa Spectrum

Mar 2025 to Present

Backend engineering across HR, payroll, billing and business infrastructure within an integrated HR technology platform.

  • Payroll engineering

    Built payroll computation functionality covering salary calculations, taxes, contributions and deductions.

  • Financial services

    Developed billing, salary funding and subscription capabilities supporting the wider HR platform.

  • Communication infrastructure

    Built internal email delivery infrastructure supporting multiple providers, templates and reliable asynchronous delivery.

NestJSTypeScriptMongoDBRedis

Architecture explorer

A simplified model of Tour, the travel marketplace I built at Ankur, now in production. Pick an item to book, break a service, and watch what the rest of the system does.

Loading the explorer

Case studies

Short decision records. Context, constraints, the decision, and what it cost.

Tour: a multi-service travel marketplace

NestJSPostgreSQLRabbitMQRedisElasticsearchDocker

Context

Tour is a travel marketplace running in production at thetourafrica.com. Customers book events, stays and cars. Vendors list them and are paid out. It runs as about six independently deployable NestJS services on PostgreSQL: Identity, Catalog, Booking, Payment, Search and Notification. They talk through RabbitMQ events and authenticated service-to-service calls.

I worked on the platform design (service boundaries, API contracts, domain model) and built Identity, Catalog, Booking and Notification. Identity covers accounts, OTP login and token handling, and vendor onboarding with KYC checks. Catalog covers events with tickets, stays with rooms, cars, promotions, places, media uploads, and the dashboards vendors see. Booking owns the reservation lifecycle. Notification runs on RabbitMQ with templates, retries, dead-letter queues and delivery tracking. I also built the search workflow: catalog changes are published over RabbitMQ and indexed in Elasticsearch, so one search spans events, stays and cars. On the money side I fixed the double-entry ledger behind customer virtual accounts, fees, settlements and refunds.

Constraints

  • One booking touches several services. There is no shared database, and a customer must never be charged twice or lose a booking because a message was dropped.
  • An item must not be sold twice while a customer is paying for it.
  • Money has to reconcile. Customer payments, fees, vendor settlements and refunds all have to balance.
  • Vendors need identity checks before they can list anything.
  • Search covers every kind of listing and has to follow catalog changes closely.

Decision

  • Transactional outbox. Identity, Catalog and Booking write each event to an outbox table in the same database transaction as the change. A relay worker publishes it to RabbitMQ afterwards. A crash between the write and the publish cannot lose an event.
  • Idempotent consumers. Booking's payment-event consumer deduplicates on a correlation id, retries with exponential backoff, and moves a message to a dead-letter queue when retries run out.
  • Short-lived holds. A booking moves from PENDING to HELD to CONFIRMED. While it is held, the item is reserved in Redis with an expiry, created atomically with a Lua script. If payment never arrives, the hold lapses and the item goes back on sale.
  • Notification as jobs. Messages are RabbitMQ jobs rendered from templates, with retries, dead-letter queues and delivery tracking. A failed email is recorded and can be replayed, and it never blocks a booking.
  • Double-entry ledger. Every money movement is recorded as balanced debit and credit entries, so fees, settlements and refunds can be traced and reconciled.
  • Catalog lifecycle. Items move from draft to review to published to archived. Updates use optimistic concurrency, and publishing sends the change to Search.
  • Simple tokens. Identity issues signed tokens that other services verify. I moved it from rotating public keys to a single shared signing secret, which simplified key handling.

Trade-offs

  • The outbox gives at-least-once delivery, so every consumer has to be idempotent. It also adds a small delay between a change and its event.
  • Redis holds are fast and need no row locks, but they are soft. A payment that lands just after a hold expires needs explicit handling.
  • A shared signing secret is simpler to run than rotating keys, but every service that verifies tokens has to keep it safe.
  • Splitting into services lets each deploy on its own, at the cost of harder debugging across them. That is why services expose metrics, use structured logs and report errors to a team channel.
  • Search is eventually consistent with the catalog. A listing can take a moment to appear after it is published.

A bug worth telling

Outbox events stopped reaching RabbitMQ, with no error. The cause was the framework's scheduling layer, not the broker or the database. I replaced the scheduled job with a dedicated relay worker that owns its polling loop and skips a run if the last one is still going. It claims rows with a row lock so several instances can run safely, retries failures with exponential backoff, and after a bounded number of attempts marks the event dead-letter instead of dropping it. It showed up as events missing downstream: services that should have reacted to a change never heard about it.

Result

  • Tour is live in production, serving customers and vendors across about six services.
  • Events are stored with the data they describe, retried with backoff, and dead-lettered when they cannot be delivered. A failed publish leaves a visible record instead of a silent gap.
  • Holds are created atomically and expire on their own, so an abandoned checkout releases the item without anyone cleaning up.
  • Vendors go through onboarding with identity checks, and a customer can search events, stays and cars from one place.
  • Money moves through a ledger where every entry has a counterpart. The problems that broke that guarantee are fixed: duplicate postings, unbalanced entries and rounding, settlement and refund mismatches, and races between concurrent updates.

Stack

What I reach for, grouped by the job it does.

Languages

  • Node.js / TypeScript
  • Go
  • Python
  • C#
  • Java

Frameworks

  • NestJS
  • Express
  • Gin
  • .NET
  • Spring Boot

Data

  • PostgreSQL
  • MongoDB
  • Redis
  • Elasticsearch

Messaging and cloud

  • RabbitMQ
  • AWS
  • GCP

APIs

  • REST
  • GraphQL
  • gRPC

Selected repos

Public work you can read.

ThryifePay GoIn progress

A payment service provider written in Go. Scope: a bank simulator as the upstream, merchant API keys, webhooks, a double-entry ledger, and settlement with payouts.

Double-entry ledger design and idempotent payment flows.

private

Notes

Short technical write-ups. Drafts are marked.

All notes

Contact

Email is the fastest way to reach me.

brysonilesanmi@gmail.com

LinkedInGitHub
Ayomide Ilesanmi, Lagoscommit feec46d · built 2026-11-13 07:18 UTC