Skip to content

The Architecture Review Checklist Nobody Actually Follows

Software architecture is the set of decisions that are expensive to change later. Let that sink in for a second. A poorly designed database schema can take months to migrate. A tightly coupled monolith can take years to decompose. A missing security boundary can take one incident to become a career-ending data breach. And yet most teams treat architecture reviews like a dentist appointment. They know they should do it. They just… don’t.

I came across a solid architecture review checklist from Ardura Consulting that breaks the evaluation into six dimensions: scalability, security, maintainability, performance, deployment, and documentation. And look, checklists aren’t sexy. But after spending years watching teams discover fundamental problems after they’ve already shipped to production and onboarded three enterprise customers, I’ve become a checklist believer. So let’s walk through each dimension and talk about what actually matters, what’s overengineered, and where most teams completely drop the ball.

Scalability: You Probably Don’t Need What You Think You Need

Here’s where every junior architect goes wrong. They hear „scalability“ and immediately start drawing diagrams with seventeen microservices, a message queue, and three different databases. Calm down.

Start with a back of the envelope estimation. How many daily active users do you have? Multiply by two because traffic spikes are real. That gives you a rough number of requests you need to serve. And here’s the thing most people won’t tell you: a deployment with three replicas, if implemented correctly, can serve hundreds of requests per second. That’s enough for the vast majority of applications.

But you should always have the backup plan. The key to truly horizontal scaling is embarrassingly simple. Don’t store state inside your application server. No sessions in memory. No file uploads sitting on the local disk. Put everything in a database or a cache. The moment you do that, spinning up new replicas becomes a non-event.

Single points of failure are the real killer. A single database, a single service instance, a single load balancer. If you want to guarantee an SLA of 99.99%, you need a highly available setup spread across multiple availability zones. Sometimes you can get away with a single instance if your SLA is 99% or even 99.5%. That’s a cost optimization, and it’s valid. But know you’re making that tradeoff.

Now here’s where it gets interesting. Can your components scale independently? This is where the modular monolith conversation comes in. Look at what Grafana did with Loki. You can deploy it as a single binary where ingestion and querying run in the same process, communicating through memory without touching the network. Or you can split them into separate deployments and scale each independently. Ingestion is almost always a completely different beast than querying. Different traffic patterns, different resource profiles. You want the flexibility to scale them separately without having to rewrite your entire system.

The database scaling question is where things get genuinely complicated. If you need maximum horizontal scalability, you’re looking at leaderless databases like Cassandra or DynamoDB. They’re optimized for writes and can handle reads to a certain degree, but with tradeoffs. Single-leader databases like PostgreSQL scale vertically, and you can add read replicas to handle more read traffic. But the write path still goes through the single leader, and that can become your bottleneck. You need to understand your read/write ratio before picking a database. Write a design doc. Do the estimation. This is not a decision you want to reverse six months in.

Caching is one of those things where the conventional wisdom is wrong. You know the joke. There are two hard problems in computer science: naming things and cache invalidation. Different caching patterns exist. Write-through, cache-aside, read-through. They all have different tradeoffs. But here’s my take: you probably don’t need caching from the start. Your database already has a caching layer built in. It loads frequently accessed data into memory. Run your back of the envelope numbers and decide if you actually need an external cache before you introduce the complexity of one.

For pagination, I keep things simple. Cursor-based pagination, max 100 items per page. The endpoint stays fast no matter how much data is behind it. If someone needs more, we can talk, but I almost never go above 1,000 items. AWS S3 does the same thing with their list API, and they seem to be doing okay.

Data archiving is a cost optimization, not a day-one requirement. In AWS S3 you can use tiered storage for frequently accessed data and cold storage for the rest. We had audit logs stored in a database for three months and then exported to cold storage because 99% of users only looked at the last day or week.

And traffic handling. Before you publish any system, do the back of the envelope calculation and then run a game day. That’s the AWS term for load testing where you deliberately try to break your system. Go past 5x your expected traffic to find the real boundaries. Have monitoring in place so you can see exactly when requests start degrading. Most of the time you don’t need auto-scaling from day one. It introduces a lot of moving parts and it’s hard to debug why replicas are spinning up and down. I prefer to alert on 5XX errors and high latency, then adjust manually. But if you know you’re building a high-throughput system, add auto-scaling from the start. In Kubernetes you can use KEDA and define custom metrics based on request rates at your reverse proxy.

Rate limiting is where most teams fail. They just don’t implement it. Then they get taken down by a user who accidentally wrote an infinite retry loop. The system resources get exhausted, everything goes down, and suddenly it’s 2 AM and you’re on a war call. Have rate limiting in place with reasonable limits. It’s not optional.

Security: The Dimension That Will Actually Get You Fired

Authentication and authorization should be centralized. One identity provider. Not per-service auth logic. Use well-established providers and patterns. You can use an API gateway for REST or GraphQL, and on the frontend, the OAuth application flow. No application should implement its own authentication mechanism. Period.

Authorization needs to be enforced at every layer. API gateway, service layer, database. This is the zero trust principle. Every single endpoint needs authorization with roles in place. Don’t trust network ACLs alone. Give services explicit credentials with explicit roles. Sometimes read-only, sometimes write access, sometimes admin.

Least privilege is non-negotiable. You never want a user with superuser rights because if that account gets compromised, you’re done. Fine-grained permissions limit the blast radius when something goes wrong. And something will go wrong.

Secrets belong in a vault. Not in environment files. Not hardcoded in the binary. For any production deployment, mount secrets from a central secrets manager. This is table stakes in 2026 and I still see teams with AWS access keys committed to their repos.

For data protection, enforce TLS 1.2+ everywhere. It’s like five lines of config in your reverse proxy. Encrypt sensitive data at rest with bcrypt or Argon2 for passwords. Enable server-side encryption on your cloud storage by default. The physical disk can get stolen from a data center. It’s unlikely, but the encryption is free, so why not.

GDPR compliance is less scary than people make it sound. Write a deletion concept that documents what data is stored where and for how long. You can even set retention to infinite for most data, as long as you have a process for handling deletion requests.

For your attack surface, the same rate limiting we talked about applies here, plus input validation in your business logic and presentation layer. Contract-based testing for your APIs helps ensure that validation doesn’t drift over time.

Service-to-service communication is where it gets debatable. If you’re running Istio, you get mTLS for free with the Envoy sidecar. But Istio brings a lot of operational overhead. If you’re in Kubernetes without a service mesh, you can use NetworkPolicies and internal DNS. If you’re inside the castle, do you really need TLS for internal traffic? It’s debatable. But for anything external, TLS is mandatory.

Scan your dependencies for vulnerabilities. Aqua, Snyk, whatever tool works for you. Put it in your CI pipeline and your runtime. And write a security incident response plan. Yes, it’s annoying. Yes, you’ll be glad you have it when something breaks at 3 AM.

Maintainability: Where Technical Debt Becomes Technical Bankruptcy

Organize your codebase by domain or feature, not by technical layer. Once a codebase reaches a certain size, vertical slices make way more sense. Within each slice, you can still have the standard layers: presentation, business logic, data. But the top-level organization should be by feature.

Clear boundaries between modules and services matter. No circular dependencies. This is hard to measure in practice. For service-to-service communication, distributed tracing can build a dependency graph that makes it visible. For code-level dependencies, your compiler catches the worst offenses and tools like SonarQube can help with the rest.

Consistent coding standards enforced through linting and CI pipelines, not through code review arguments. Put the rules in a linter config. Run it in pre-commit hooks. Stop fighting about formatting in pull requests.

Test coverage should be above 80% for critical paths. I enforce this via SonarQube. I know there are arguments about whether coverage numbers actually mean anything, and in my opinion, most teams aren’t experienced enough to make that judgment call. So just set the rule. Even if it feels stupid, you’ll thank me later.

Each service a team owns should be independently deployable. If deploying one service requires coordinating with another team, you have a coupling problem. Individual CI/CD pipelines per service, communication over network interfaces, and good testing strategies make this possible.

Feature flags are great but not always necessary. Canary deployments can serve the same purpose. Slowly increase traffic to the new version. If something breaks, roll back. Not all users are affected. The blast radius is small. You get the same safety without the complexity of a feature flag system.

Your CI/CD pipeline should produce a deployable artifact in under 15 minutes. I’ve seen teams with 30-minute, even 60-minute pipelines and it destroys their velocity. The fix is usually the same: too many integration tests, not enough unit tests. Run fast linting and unit tests in CI. Save integration and end-to-end tests for a real staging environment.

Knowledge distribution is critical. The bus factor for any critical component must be greater than one. This means documentation, ADRs, and an onboarding process that lets a new developer set up the environment and make a change in under an hour. Use Makefiles, dev containers, established patterns like hexagonal architecture. Don’t reinvent the wheel. If you use Angular or Symfony, the framework is opinionated. Follow the opinions. That means every codebase looks the same, and that’s the point.

Keep your system diagrams current. Updated within the last six months at minimum. Write it into your definition of done: a feature isn’t done until tests pass, it’s deployed to prod, and the documentation is updated. This is where AI agents can actually help. Build a pipeline that keeps your docs in sync.

Performance: Measure First, Optimize Second

Define response time targets per endpoint. API calls under 200ms at P95. Page loads under two seconds. You can aim for P99 at 15ms. It’s achievable and doesn’t cost much if you follow standard patterns. Document these targets, run regular performance tests, and set SLO alerts in your observability stack. The key is that you actually fix the issues when the alerts fire. Sounds obvious, but I’ve seen teams with dashboards full of red indicators that nobody looks at.

Build a dashboard showing the top ten slowest endpoints by P99 latency. Every microservice should have one. Sort by verb and path if you’re in REST. Don’t forget the SLO alerts because you won’t be staring at the dashboard every day.

Most teams never run EXPLAIN ANALYZE on their queries. They use an ORM and call it a day. And honestly, that’s fine most of the time. CRUD operations don’t need hand-tuned SQL. But when you hit the limits of what the ORM can do, you need to optimize manually. My approach: monitor API response times, act when SLOs aren’t met. Don’t optimize prematurely.

Caching at the application level is a mature optimization. Don’t introduce it from the start but build your code structure to support it. Connection pooling should be handled by your framework or by an external tool like PgBouncer. Either way, you need it, because connection exhaustion under load will take your system down faster than almost anything else.

Separate your batch processing from your synchronous APIs. Batch jobs degrading API performance is one of the most common problems I see. The synchronous path is customer-facing. If that degrades, customers notice immediately. If an async background job slows down, it’s still bad, but the customer doesn’t feel it during their interaction.

Distributed tracing, meaningful business metrics (not just infrastructure metrics), alerts for degradation (not just outages), and structured JSON logs with correlation IDs. This is the observability stack you need. It’s not complicated. It’s just that most teams set up the tools but never configure them properly.

Deployment: Automate Everything, Trust Nothing

Full automation from commit to production. No manual steps. No SSH-ing into a VM to run commands. No clicking buttons in a console. This is the baseline expectation and it’s easy to implement. Every minute an engineer spends on manual deployment steps is a minute wasted.

A staging environment that mirrors production is a best practice, but let’s be honest. It’s almost never truly achieved. Production has more load, more data, and the cost of mirroring is prohibitively high. Use canary deployments in production instead. It’s a better use of your money.

Database migrations are where teams get burned. Large tables lock entirely during certain migrations, blocking all reads and writes. Test your migrations in staging, especially the rollback path. Most engineers have never rolled back a migration because they’ve never practiced it. Add it to your disaster recovery process.

Can your team deploy multiple times per day without heroics? This requires fast pipelines, good testing, and confidence in your rollback mechanism. Test the rollback regularly. Document the process. Verify it works.

Zero-downtime deployments should be the default. In Kubernetes you get this for free. In AWS with VMs, use auto-scaling groups with a load balancer and blue-green or canary deployment configs. No one should be seeing maintenance windows for standard releases.

Monitor deployment metrics. Error rate and latency after a deploy compared to before. Set up alerts so you don’t have to stare at a dashboard every time you push code. You want to know when something breaks, not babysit every release.

Infrastructure as code. Terraform, Pulumi, CloudFormation. Whatever you use, check it into version control. You will forget what you clicked in the AWS console. Version control gives you auditability and the four-eyes review process. Can you provision a new environment from scratch automatically? If you’ve got IaC and you’ve tested it during a disaster recovery exercise, yes. If not, you’ve got work to do.

Documentation: The Dimension Everyone Skips and Then Regrets

Architecture diagrams at multiple levels of abstraction. C4 model: context, container, component. These help onboard new engineers and remind existing engineers how the system actually works. But they have to be current. Diagrams that reflect what you planned two years ago instead of what you built are worse than no diagrams because they actively mislead people.

ADRs for significant decisions. When someone asks „why did we choose Kafka over RabbitMQ?“ you point them to the ADR with the context, the alternatives evaluated, and the rationale. No room for endless circular discussions.

Integration points need documentation as the system grows. Which services talk to which other services, through what APIs, using what data contracts. Yes, it’s all in the code. But understanding the system at a high level shouldn’t require reading through ten repositories.

Runbooks for common operational tasks. Scaling, failover, data recovery. When an alert fires at 2 AM, the on-call engineer should know exactly what to do. If they don’t, your runbooks need work. Document the incident response process too. Who to call, how to diagnose, how to communicate. When you’re stressed and customers are complaining, you don’t want to be figuring out process.

Document your monitoring dashboards. What each metric means, what thresholds are concerning. Dashboards tend to accumulate and look similar, and if you don’t work on the system daily, you’ll spend ten minutes clicking through dashboards trying to find the right one. Link them from your alerts.

API documentation. OpenAPI, Swagger, GraphQL schema docs. Generate it from code so it doesn’t drift from reality. Or write the spec first and generate code from it. Both approaches work. Just don’t maintain both manually. Version your APIs. Every breaking change gets a new version. The client should never break because you pushed a release. Provide examples for common use cases directly in the documentation.

The Uncomfortable Truth

Here’s the part nobody wants to hear. Most teams score poorly on at least three of these six dimensions. And the dimensions they skip are almost always security, documentation, and maintainability. The „boring“ stuff. The stuff that doesn’t ship features. The stuff that only matters when things go wrong.

But things will go wrong. And when they do, the teams that invested in these fundamentals recover in minutes while everyone else recovers in days. Do the checklist. Score yourself honestly. Fix the gaps before they fix you.

DSGVO Cookie Consent mit Real Cookie Banner