Skip to main content

Posts

Designing Tenant Isolation Before the Data Model Becomes Expensive to Change

You're building a SaaS product or internal platform that serves multiple customers, business units, or teams. Right now, you have a single database, a shared schema, and a WHERE clause filtering on tenant_id. It works. The question is: will it still work when you need to move a high-value customer to dedicated infrastructure, when compliance requires physical separation, or when one tenant's query load starts degrading performance for everyone else? Tenant isolation is not just a security concern. It's an architectural decision that shapes your data model, your query patterns, your deployment topology, and your operational flexibility. Make the wrong choice early, and you'll face expensive migrations under pressure. Make the right choice, and you preserve options without over-engineering. Isolation Models and Their Tradeoffs There are three common isolation models: shared schema with row-level filtering, schema-per-tenant, and database-per-tenant. Each has a diff...
Recent posts

When to Split a Microservice and When to Keep It Together

You have a monolithic .NET service handling customer orders, inventory checks, and email notifications. A colleague suggests splitting it into three microservices. Another argues the split will triple operational overhead without delivering business value. Both are probably right, but neither answer helps you decide what to do Monday morning. After modernizing multiple legacy .NET systems and operating microservices architectures on both AWS and Azure, I've learned that the decision to split a service is less about embracing distributed systems and more about matching architectural boundaries to the operational consequences your team can actually handle. The wrong split creates alert fatigue, deployment dependencies, and debugging sessions that span five repositories. The right boundary reduces coupling, enables independent deployments, and makes production incidents easier to isolate. Start With the Deployment Boundary, Not the Domain Model The first question is not whether...

What Automated Tests Should Protect First in a Legacy .NET Modernization

When you inherit a legacy .NET system with no automated tests, the instinct is to start writing unit tests everywhere. But modernizing production systems under time pressure forces a harder question: which tests give you enough safety to refactor confidently without spending months writing coverage that never catches real bugs? I've modernized legacy .NET systems into testable architectures using .NET 6, microservices, and CI/CD pipelines, and the answer is not "test everything equally." The answer is to protect the boundaries where your system exchanges value with the outside world, then work inward only as far as velocity demands. Test the Integration Points That Break Silently The highest-value tests in a legacy modernization protect integration points: REST APIs, database queries, WCF service calls, file parsers, and external connectors. These are the places where production failures hide behind optimistic assumptions. A missing null check in a WCF integration do...

AWS Lambda Runtime Credentials in Secrets Manager: Why I Stopped Putting Them in Environment Variables

When you build an AWS Lambda function that needs to call external APIs—Blogger, Slack, a payment provider, anything that requires an API key or OAuth token—you face a small decision that has outsized consequences for your operational security posture. Do you store those credentials in Lambda environment variables, or do you fetch them at runtime from AWS Secrets Manager? The first option is faster and simpler. The second requires a few extra lines of code, adds a network call to every cold start, and costs a few cents per month. I chose Secrets Manager for the blog agent, and I would make the same choice again for any Lambda that touches sensitive credentials. The Real Decision: Visibility Versus Runtime Fetching Lambda environment variables are encrypted at rest using AWS-managed keys, and they are decrypted automatically when your function starts. They are visible in the Lambda console to anyone with read permissions on the function. They appear in CloudFormation templates, in C...

AWS Lambda Versus Containers: Choose From Execution Shape, Not Cloud Fashion

Should you deploy next service as an AWS Lambda function or run it in containers on ECS or Kubernetes. The question usually arrives wrapped in anxiety about making the "modern" choice, as if one option is inherently more sophisticated than the other. The real decision has nothing to do with fashion. It comes down to execution shape: how your code actually runs, how long it needs to run, and what operational complexity you're willing to accept in exchange for specific runtime guarantees. The Question That Actually Matters: What Does Your Code Do When It Runs? Lambda and containers solve different problems. Lambda is an event-driven execution model optimized for short-lived, stateless work triggered by discrete events. Containers are long-lived processes that you manage, scale, and monitor as running services. The choice is not about which technology is newer or more impressive on a résumé. It is about matching the execution model to the actual behavior of your workloa...

Feature Flags Fixed My Team's Release Branch Problem

If you've ever watched a release slip because someone forgot to cherry-pick one commit onto the release branch, or spent an afternoon untangling why develop and main disagree about what's actually live, you've already run into this problem. The branching model usually gets blamed, but it's rarely the real issue — it's a mismatch between the strategy you copied and your actual deploy frequency, whether you need multiple versions live at once, and which manual steps nobody's gotten around to automating. Here's what I'd actually tell a team picking a branching strategy today, with the specifics that usually get skipped in the "GitFlow vs. trunk-based" comparisons. Deploy Frequency: The One Number That Decides Everything The branching debate isn't really about GitFlow vs. trunk-based philosophy — it's about how often you ship and how many versions need to be live simultaneously: Deploying daily or more fits trunk-based development...

Building a SaaS on AWS: The Technical Decisions That Actually Matter

Every few months someone asks me a version of the same question: "Should I just build my SaaS on AWS, or is that overkill for a small team?" The honest answer is: it depends less on AWS itself and more on the specific decisions you make on top of it — service selection, data modeling, cost controls, and where you deliberately choose not to over-engineer. Here's what I'd actually tell an engineering team building a SaaS product on AWS today, with the specifics that usually get skipped in the "10 AWS services for your startup" listicles. The Compute Layer: Pick Based on Execution Shape, Not Hype The Lambda-vs-containers debate isn't really about serverless philosophy — it's about execution shape: AWS Lambda fits short-lived, stateless, bursty work: API handlers, webhook processors, scheduled jobs. Watch two numbers: cold start latency (still meaningfully worse for Lambdas attached to a VPC, due to ENI creation — mitigated but not eliminate...