Technologies

Not a logo wall.
A way of working.

Anyone can list tools. What matters is knowing what each one is for, when to reach for it, and how to use it without leaving a mess for the next person. Here are 29 technologies across 9 domains — each with what it is, why I use it, where I've used it, and a practice I actually hold to.

01

Backend

Where most of my work lives. Backend engineering is about guarantees — that data is consistent, messages arrive, and failures are recoverable rather than silent.

.NET / C#

A mature, statically-typed runtime and language from Microsoft for building high-performance server applications.

Why I use it

When a system processes payments or synchronizes records across CRMs, I want a runtime I understand to its edges — the type system, the async model, the GC behaviour. .NET gives me that depth plus genuinely excellent performance.

Where I've used it

My primary language across enterprise APIs, integration services, and background workers — including the proxy service layer that fronted every third-party integration at a national charging network.

Best practice

Lean on the type system. Make illegal states unrepresentable so the compiler catches mistakes before they reach a test, let alone production.

ASP.NET Core

The web framework within .NET for building APIs and services.

Why I use it

It's fast, the middleware pipeline is explicit and composable, and dependency injection is built in rather than bolted on. I can reason about exactly what happens to a request.

Where I've used it

Every HTTP API I've built in the Microsoft ecosystem — REST services, webhooks for payment providers, and internal contracts between microservices.

Best practice

Keep controllers thin. They translate HTTP to intent and nothing more; the actual work belongs in the domain, where it can be tested without a web server.

Entity Framework Core

An object-relational mapper that maps C# objects to relational database tables.

Why I use it

It removes a vast amount of boilerplate while still letting me drop to raw SQL when a query needs hand-tuning. The migration tooling makes schema evolution disciplined.

Where I've used it

Data access across most .NET services I've built on SQL Server and PostgreSQL.

Best practice

Watch for N+1 queries relentlessly. An ORM makes data access easy, which is exactly why it makes performance mistakes easy too — profile the generated SQL.

02

Commerce

Commerce rewards depth. The hard problems — pricing, fulfilment, approval flows — almost never live in the frontend. They live in the model underneath.

Commercetools

An API-first, headless commerce platform built for complex B2B and enterprise catalogs.

Why I use it

When pricing has thousands of contract permutations and the storefront is just one of several channels, an API-first core that doesn't dictate the frontend is exactly right.

Where I've used it

The commerce core of a multi-tenant B2B platform for an industrial distributor, integrated with SAP ERP and a custom pricing engine.

Best practice

Don't push business logic into the platform that doesn't belong there. Keep custom pricing and approval logic in your own services where you can test and own them.

Shopify Plus

An enterprise tier of Shopify with extended APIs, custom checkout, and higher throughput.

Why I use it

For direct-to-consumer, nothing gets a brand to revenue faster. The platform handles the commodity problems so the work goes into what's actually differentiated.

Where I've used it

Multiple D2C storefronts for content creators — custom apps, Liquid themes, and automated lifecycle flows, brand to revenue in under 60 days each.

Best practice

Respect the platform's boundaries. Fighting Shopify's conventions costs more than it saves; extend through apps and webhooks rather than working against the grain.

Optimizely

A combined CMS and commerce platform in the .NET ecosystem, strong on content-led commerce.

Why I use it

For content-heavy commerce in the Microsoft stack, it unifies editorial and selling in one place — valuable when the brand is the product.

Where I've used it

A multi-brand CMS migration for one of Scandinavia's largest media groups, moving years of content off a legacy system.

Best practice

Model content types deliberately up front. Editorial teams live in those structures every day; a sloppy content model is a daily tax on the people using it.

03

Cloud

I build cloud-native by default, primarily on Azure. The goal is systems that scale with demand and stay observable when something inevitably goes wrong.

Azure Container Apps

A serverless container runtime that handles orchestration, scaling, and networking without managing Kubernetes directly.

Why I use it

It gives me most of the power of Kubernetes with a fraction of the operational weight — the right default when a team doesn't need full cluster control.

Where I've used it

The runtime for the microservices platform I rebuilt at a national EV charging operator, scaling across seven sales channels.

Best practice

Set sane scaling rules and resource limits from the start. Serverless still costs money, and an unbounded scale rule is a budget incident waiting to happen.

Azure Service Bus

A managed message broker for reliable asynchronous communication between services.

Why I use it

Asynchronous messaging is how you decouple services so a slow or failing downstream doesn't cascade. Service Bus brings the delivery guarantees and dead-lettering that make that safe.

Where I've used it

The asynchronous backbone between domain services in the integration platform — so a struggling provider degraded one feature instead of the whole system.

Best practice

Design every handler to be idempotent. At-least-once delivery means messages will be redelivered; safe retries are a requirement, not a nicety.

Azure API Management

An API gateway for publishing, securing, and observing APIs behind a single front door.

Why I use it

Centralizing auth, routing, and rate limiting at the edge keeps that cross-cutting concern out of every individual service.

Where I've used it

The gateway fronting all third-party integrations at the charging network — one place to observe and control traffic to external systems.

Best practice

Use it as a policy layer, not a logic layer. Routing and rate limiting belong here; business rules belong in services where they're testable.

04

Databases

The data layer is where correctness is won or lost. I pick the store that fits the access pattern rather than defaulting to a favourite.

PostgreSQL

A powerful open-source relational database with strong standards compliance and rich extension support.

Why I use it

My default for new relational work outside the Microsoft stack — rock-solid, feature-rich, and it scales further on a single node than people expect.

Where I've used it

Relational data in independent client projects and the systems I'm building at VelaCommerce.

Best practice

Index for your actual query patterns, not hypothetically. Then verify with EXPLAIN ANALYZE — assumptions about query plans are frequently wrong.

SQL Server

Microsoft's enterprise relational database, deeply integrated with the .NET ecosystem.

Why I use it

When ACID guarantees matter and the stack is already Microsoft, the tooling and integration are hard to beat.

Where I've used it

The transactional core of enterprise .NET systems, including the B2B commerce platform where pricing had to be correct to the cent.

Best practice

Keep transactions short and tightly scoped. Long-held locks are a leading cause of mysterious production slowdowns under load.

MongoDB

A document database that stores flexible, schema-light JSON-like records.

Why I use it

When data is genuinely document-shaped or schema-flexible — integration payloads, event logs — forcing it into rigid tables is friction without benefit.

Where I've used it

Storage in the event-driven services at the charging network, where integration data didn't fit a fixed relational schema.

Best practice

Schema-less doesn't mean structure-less. Design your document shapes around read patterns deliberately, or you trade rigidity for chaos.

Redis

An in-memory data store used for caching, sessions, and ephemeral high-speed data.

Why I use it

When something needs to be fast and is acceptable to lose, in-memory is the answer. It takes pressure off the primary database cheaply.

Where I've used it

Caching and distributed rate limiting across services that needed to shield slower downstreams from load.

Best practice

Always set TTLs and have an explicit invalidation strategy. A cache without an eviction plan eventually becomes a second, stale source of truth.

05

Infrastructure

Infrastructure should be reproducible and reviewable. If I can't recreate an environment from code, I don't really control it.

Docker

A containerization tool that packages an application with its dependencies into a portable image.

Why I use it

It eliminates 'works on my machine' by making the runtime environment explicit and identical from local development to production.

Where I've used it

Containerization across every modern service I've built, from local dev through to Azure Container Apps in production.

Best practice

Use multi-stage builds and minimal base images. Smaller images deploy faster, expose less attack surface, and are simpler to reason about.

Kubernetes

A container orchestration platform that automates deployment, scaling, and management of containers.

Why I use it

When orchestration complexity is genuinely warranted, it's the standard for good reason. I also know when it's overkill — most teams reach for it too early.

Where I've used it

Container orchestration where the scale and team topology justified the operational investment over lighter-weight options.

Best practice

Don't adopt it by default. The operational cost is real; reach for managed runtimes first and graduate to Kubernetes only when you've outgrown them.

Infrastructure as Code

Defining infrastructure — networks, services, resources — in version-controlled declarative files.

Why I use it

Reproducibility and review. Infrastructure changes go through the same scrutiny as application code, and environments can be torn down and rebuilt with confidence.

Where I've used it

Provisioning the full Azure environment for the integration platform, so the infrastructure was as reviewable as the services running on it.

Best practice

Keep environments in parity through the same definitions. Drift between staging and production is where the worst surprises are born.

06

DevOps

Shipping should be boring. The more automated and observable the path to production, the more attention is freed for the actual engineering.

CI/CD Pipelines

Automated pipelines that build, test, and deploy code on every change.

Why I use it

Manual deployment is a source of human error and a brake on iteration. A good pipeline makes shipping a non-event.

Where I've used it

Build-and-deploy automation across the systems I've owned, gating merges on tests and deploying containers automatically.

Best practice

Make the pipeline the only path to production. The moment manual deploys are possible, they happen — usually at the worst time.

Azure Monitor

Azure's platform for collecting and analysing logs, metrics, and traces across services.

Why I use it

You cannot improve what you can't see. Observability turns 'something is slow' from a guess into a question with an answer.

Where I've used it

Diagnostics and fault tracing at the charging network, where a request might cross four services before reaching an external system.

Best practice

Instrument with a shared correlation ID from day one so a single request can be followed across every service boundary it crosses.

Git

A distributed version control system for tracking changes and collaborating on code.

Why I use it

It's the substrate everything else builds on — review, history, and the ability to understand why a system is the way it is.

Where I've used it

Every project, always. The commit history is documentation of intent when it's written with care.

Best practice

Write commits that explain the why, not the what. The diff already shows what changed; the future reader needs to know the reason.

07

Frontend

I'm a backend engineer first, but I build frontends that need to be fast and maintainable — with the same discipline I bring to services.

TypeScript

A typed superset of JavaScript that adds static type checking.

Why I use it

Types catch a whole class of bugs before runtime and make refactoring safe. I won't start a serious frontend project without it.

Where I've used it

All frontend work and Node.js services — including this site and the storefronts I've built on Next.js.

Best practice

Run in strict mode and resist 'any'. Each escape hatch is a small hole in the safety net the type system exists to provide.

Next.js

A React framework with server rendering, static generation, and file-based routing.

Why I use it

It handles rendering strategy, routing, and performance concerns out of the box, so the effort goes into the product rather than the plumbing.

Where I've used it

The self-service portal for the B2B commerce platform, several D2C storefronts, and this website.

Best practice

Render on the server by default and reach for client components only where interactivity demands it. Ship less JavaScript to the browser.

Tailwind CSS

A utility-first CSS framework for styling directly in markup.

Why I use it

It keeps styling co-located with structure and enforces a consistent design scale, which keeps a UI coherent as it grows.

Where I've used it

The styling system for this site and recent frontend builds, paired with a small set of design tokens.

Best practice

Anchor everything to design tokens — spacing, colour, type scale. Utilities without a system underneath just relocate the inconsistency.

08

Testing

Tests are how I sleep. Not for coverage numbers — for the confidence to change a system without holding my breath.

xUnit

A unit testing framework for .NET.

Why I use it

Fast, focused tests on the logic that matters most — pricing, validation, the rules a business actually depends on.

Where I've used it

Exhaustive tests around the pricing engine in the B2B platform, where a wrong price was a contractual problem, not a UX one.

Best practice

Test behaviour, not implementation. Tests coupled to internal structure break on every refactor and discourage the cleanup code needs.

Integration Testing

Tests that exercise multiple components together — service, database, message bus — rather than in isolation.

Why I use it

Unit tests prove pieces work alone; integration tests prove they work together, which is where real systems actually fail.

Where I've used it

Verifying the ERP sync layer in the B2B platform — the seam where two sources of truth had to stay honest with each other.

Best practice

Test the seams between systems hardest. Integration boundaries are where assumptions diverge and where the expensive bugs hide.

Idempotency by Design

Building operations so that running them more than once has the same effect as running them once.

Why I use it

In distributed systems, retries are guaranteed. Idempotency is what makes them safe rather than a source of duplicate charges or corrupt state.

Where I've used it

Message handlers across the event-driven platform, and the re-runnable migration pipeline that moved a decade of content safely.

Best practice

Treat idempotency as a design property from the outset. It's painful to retrofit onto handlers that assumed they'd run exactly once.

09

Architecture

Architecture is the set of decisions that are expensive to reverse. I try to make fewer of them, later, and with better information.

Microservices

An architectural style of small, independently deployable services owning distinct capabilities.

Why I use it

Powerful when team topology and domain boundaries justify the operational overhead — and a costly mistake when they don't. The judgment is knowing which.

Where I've used it

The platform rebuild at the charging network, where independent teams and distinct domains made the boundaries genuinely worthwhile.

Best practice

Let the boundaries follow the organisation, not the org chart's wishes. Services that don't map to real team ownership become distributed monoliths.

Event-Driven Architecture

A style where services communicate by emitting and reacting to events rather than calling each other directly.

Why I use it

It decouples services in time and dependency, so they can fail and recover independently instead of bringing each other down.

Where I've used it

Communication between domain services in the integration platform, where loose coupling was the whole point of the rebuild.

Best practice

Design explicitly for partial failure — dead-letter queues, retries, circuit breakers. The question is never if a downstream fails, only what happens when it does.

The Strangler Pattern

Incrementally replacing a legacy system by routing functionality to new components piece by piece.

Why I use it

It turns a terrifying big-bang rewrite into a series of reversible steps, each small enough to validate and roll back.

Where I've used it

Migrating live third-party integrations onto a new proxy layer at the charging network — with no maintenance window available.

Best practice

Keep the old path warm as a fallback until confidence is high. Shadow traffic first, then reads, then writes — never flip the whole system at once.

Domain Modeling

Designing software structure around the real concepts and rules of the business domain.

Why I use it

In complex domains like B2B commerce, the model is the product. Get pricing and approvals right and the UI nearly designs itself.

Where I've used it

Modeling approval workflows as configuration rather than code in the B2B platform, so new account structures needed no deploy.

Best practice

Encode what varies as data, not branches. Conditional logic for per-customer behaviour is a maintenance trap; configuration scales.

Tools serve the problem

The stack is a means, never the point

I'm comfortable in this toolset, but I don't lead with it. The right technology falls out of the problem and its constraints — not the other way around. New tools get learned when a problem genuinely calls for them.

To see these applied to real systems rather than described in isolation, the case studies are the better read.