Grading the Registration Architecture

Checking the finished registration design against the four qualities that actually decide whether it survives launch day: security, scalability, cost, and complexity.

August 29, 20264 min read5 / 12

The registration flow is fully wired now: CloudFront and WAF out front, Cognito handling the OTP challenge, API Gateway authorizing every write, a Lambda function landing data in DynamoDB. Before moving on to vehicle registration, it's worth stopping and grading what got built against the four things that actually matter.

These are the same four qualities the serverless mindset argued for from the start: security, scalability, cost, and complexity. This is where that argument gets tested against an actual design instead of staying theoretical.

A scorecard covering security, scalability, cost, and complexity for the registration architecture ExpandA scorecard covering security, scalability, cost, and complexity for the registration architecture

Security

  1. The perimeter is one door, and that door is defended. CloudFront is the only entry point, and it carries the WAF rules, so an attacker never gets a second, less-guarded path in.
  2. S3 has no direct public access. An Origin Access Identity means the bucket only accepts traffic that's already come through CloudFront, not requests aimed straight at the bucket's own URL.
  3. Service-to-service calls are signed, not just trusted. When Amplify calls Cognito, it signs the request with SigV4, a scheme that stamps each request with a hash only someone holding the right credentials could produce, so AWS can tell a genuine call from a forged one, the same mechanism any properly secured AWS API call uses.
  4. Tokens are short-lived by design. ID and access tokens expire in about an hour, and Amplify refreshes them quietly in the background instead of leaving a long-lived credential sitting around to be stolen.
  5. The database write is gated before it happens. API Gateway checks the caller's token against Cognito before the register Lambda or the database ever sees the request.

Scalability

Most of the individual pieces here (WAF, CloudFront, S3, Cognito, SNS, DynamoDB, API Gateway) are managed services, which makes them elastic on their own. That's necessary, but it's not sufficient. A pile of scalable services wired together carelessly can still produce a system with a single point of failure.

Two decisions in this design specifically protect the whole system, not just its parts.

  • Event-driven triggers instead of polling. Cognito invokes the auth challenge Lambdas only when a user is actually going through the flow. Nothing sits idle checking for work that isn't there.
  • Reserved concurrency on Lambda functions. An AWS account gets 1,000 concurrent Lambda executions by default, raisable through a support ticket, and a busy system might push that to 5,000. Without a cap, one function under heavy load could consume the entire account's concurrency and starve every other function in the system. Capping the register Lambda at, say, 100 to 200 concurrent executions keeps one busy path from taking down everything else.

Databases are also the weakest scaling link in most architectures, since they can't burst the way a function can. DynamoDB's on-demand mode absorbs a large volume of writes without any capacity planning ahead of time, which is what makes it a reasonable fit here despite that general rule.

Cost

Every service in this design (CloudFront, S3, Cognito, SNS, DynamoDB in on-demand mode, API Gateway) bills for actual usage, not for sitting idle. A quiet week after the launch spike costs close to nothing, which was the entire point of reaching for serverless first back in the mindset post.

The one thing worth watching here: some add-ons, like certain WAF managed rule groups, charge a flat fee regardless of traffic. Pay-per-use isn't universal just because most of a design is serverless. Check each service's pricing model individually rather than assuming the whole stack behaves the same way.

Complexity

A diagram with this many connected boxes looks complex at a glance. It isn't, and the reason is worth naming directly: almost none of these boxes contain custom code.

Two Lambda functions hold the only application logic in the entire flow, the auth challenge handlers and the register function. Everything else is a managed service being configured, not a system being built from scratch. Defined through infrastructure as code, CDK or the Serverless Framework, this entire registration architecture is something one engineer could stand up in days, not months.

That's the actual payoff of the tradeoff from the first post in this series: pushing complexity into the architecture only pays off if the pieces you're pushing it into are themselves simple to reason about. Managed services are simple to reason about. Custom infrastructure usually isn't.

Registration handles one part of getting a fuel pass. The vehicle registration step still has to run a check against an external government system that isn't built to handle the same traffic this architecture can, and that mismatch is where the next post picks up: choreography versus orchestration, and using a queue to protect a slower downstream system from a faster one.

The Essentials

  1. Grade an architecture against security, scalability, cost, and complexity, not just whether it satisfies the functional requirements.
  2. Elastic services don't automatically make an elastic system. How they're connected matters as much as what each one can individually handle.
  3. Cap Lambda concurrency deliberately. One busy function can otherwise consume an entire account's shared concurrency limit.
  4. Pay-per-use isn't universal. Check each service's pricing model, some add-ons still charge flat fees regardless of traffic.
  5. Many connected services isn't the same thing as high complexity, if most of those services are managed and only a couple of functions hold custom logic.

Further Reading and Watching