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.
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.
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.
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.
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.
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.
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.
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.
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.
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.