Oscar te Giffel®
← Writing

Where Does It Belong?.

The one question that separates senior engineers from everyone else.

The fundamental question in software isn’t “how do we build this?” It’s “where does this belong?”

It scales from the biggest decisions to the smallest ones. Where do we host our data? Which service owns this logic? What module contains this function? Where does this line of code go? Junior engineers freeze at these decision points. Senior engineers move forward because they’ve internalized the tradeoffs. They’ve felt the pain of wrong choices enough times to recognize the patterns.

Most problems can be solved at multiple layers. The skill isn’t finding a solution. It’s choosing the right layer for your constraints.

Let me show you what I mean.

One Problem, Five Layers

You’re building a SaaS platform. Each customer has a subscription tier that limits their API calls: 1,000/hour for Basic, 10,000 for Pro, 100,000 for Enterprise. Exceed the limit, get rejected.

Simple requirement. But you can implement it at five different layers, and each choice carries real consequences.

The Database

Enforce limits with triggers and stored procedures. Every insert checks the count.

Upside: Impossible to bypass. No matter which application server, background job, or rogue script touches your data, the limit holds.

Downside: Your database is now doing business logic. Databases are great at storing data and terrible at explaining why they rejected something. That cryptic SQL error needs to bubble up through multiple layers before it becomes a useful message. You’ve also coupled your rate limiting to your database vendor. Migration day just got a lot harder.

The Data Access Layer

Move up one layer. Check the count in your repository code before inserting.

Upside: Centralized logic. Every part of your app goes through the same path. You can write tests against it. Error messages are in your control.

Downside: Race conditions. Between checking the count and inserting the record, another request sneaks through. You could add transactions or locks, but now your simple check is a distributed systems problem. And you’re still hitting the database on every request.

The Application Service

Use Redis for the counting, keep your database for persistence.

Upside: Now we’re cooking. Redis is designed for exactly this. Your database isn’t being hammered. Atomic increments solve the race condition. The right data structure made the algorithm obvious. Let data drive your design. The logic is testable and the errors are clear.

Downside: Eventual consistency. Redis says 4,998 requests, your database says 5,000. For rate limiting, being off by a few is probably fine. But you’ve added a dependency. Redis goes down, your API stops working. You need fallback logic, monitoring, and someone who knows how to run Redis in production.

The API Gateway

Push it to your infrastructure. Nginx, Envoy, or whatever sits in front of your app.

Upside: Requests never reach your application. Microseconds instead of milliseconds. The most performant option by far.

Downside: Your gateway doesn’t know about subscription tiers. It can rate limit by IP or header, but it doesn’t understand your business rules. Want to add a burst feature for Black Friday? You’re editing config files and restarting infrastructure instead of deploying code. And if you’re picking this layer for “performance,” have you actually measured that the application layer is too slow? Don’t optimize for a bottleneck you haven’t proven exists.

The Client SDK

Let the client throttle itself based on its known rate limit.

Upside: Zero server overhead. For well-behaved clients, it’s elegant.

Downside: You’ve trusted the client. Any developer can remove the throttle and blast your API. You need server-side enforcement anyway, making this an optimization rather than a solution.

How to Choose

Three questions I ask myself:

What are my constraints? 100 requests a day? Database triggers are fine. 100,000 a second? You need gateway-level or Redis. No ops team? Stay in the application layer where your team is comfortable.

What changes together? If rate limits change with subscription tiers, that’s application logic, not infrastructure. If limits are purely defensive against DDoS, that’s infrastructure. Things that change together should live together.

What’s the failure mode? Database failures are catastrophic. Application failures are recoverable. Gateway failures are silent from the client’s perspective. Where do you want your system to fail, and how visibly?

Real systems rarely pick just one layer. Defense in depth works: gateway for DDoS protection, application layer for business rules, database as a final safety net.

What This Really Teaches

I learned this the hard way. I once implemented rate limiting in database triggers at a startup. Clean, impossible to bypass, single source of truth. Then the product team wanted a grace period for customers whose payments failed. Simple requirement.

It turned into a two-day database migration across three tables with a maintenance window. A five-line code change in the application layer became an infrastructure event. I’d chosen the wrong layer. Not because triggers are bad, but because my context (a startup where business logic changed weekly) made them the wrong choice. I picked clever when I should have picked simple.

Junior engineers ask “where does this go?” Senior engineers ask “what happens if I put it here instead?” The difference isn’t knowledge. It’s curiosity about consequences.

The Germans have a word for this: Fingerspitzengefühl. Fingertip feeling. The intuitive sense for making the right call that only comes from years of getting it wrong first.

The right layer isn’t about technical superiority. It’s about honest assessment of your constraints. The startup choosing database triggers because they lack ops expertise isn’t wrong. The enterprise choosing gateway-level limiting because they have a platform team isn’t over-engineering. Both are making the right call for their context.

Where does it belong? It depends on who you are, what you’re building, and what you’re willing to trade. Adapt your approach to the context.