Chapter 13: FastAPI, Pydantic, and Service Boundaries
This chapter builds a service boundary with FastAPI the same way the Java curriculum eventually builds Spring endpoints. The point is not framework tourism. The point is learning request models, validation, dependency boundaries, service layers, and API error semantics.
Why This Chapter Exists In The OrderOps Python Project
This chapter builds a service boundary with FastAPI the same way the Java curriculum eventually builds Spring endpoints. The point is not framework tourism. The point is learning request models, validation, dependency boundaries, service layers, and API error semantics.
Inside OrderOps, this chapter shows up while OrderOps is graduating from internal scripts to a small service that other tools and dashboards can call safely. The goal is not to memorize one-off syntax. The goal is to make Python code readable enough to explain, safe enough to change, and grounded enough to discuss in an interview without sounding vague.
- Project lens: OrderOps is graduating from internal scripts to a small service that other tools and dashboards can call safely
- Milestone: design a small FastAPI service with typed requests, clear validation, service orchestration, and boundary-aware error handling
- Interview lens: the next chapter strengthens Python language fluency further with decorators and context managers so infrastructure patterns stay readable
- The chapter teaches Python fundamentals through one connected backend and automation story.
Route Handlers Should Stay Thin Enough That The Web Boundary Is Still Easy To Read
Keep handlers focused on translating HTTP requests into service calls and responses.
In OrderOps, OrderOps is graduating from internal scripts to a small service that other tools and dashboards can call safely. That makes Route Handlers a real engineering concern instead of a trivia topic. It affects whether the script or service stays easy to trust when another engineer reads it six weeks later.
The common failure mode is straightforward: If route handlers own all the business logic, testing and refactoring become harder quickly. The stronger move is to make the rule explicit, keep the data shape visible, and leave a code path that is easy to narrate under interview pressure. Interviewers like thin-route answers because they reflect maintainable boundary design.
- Keep handlers focused on translating HTTP requests into service calls and responses.
- Project lens: OrderOps is graduating from internal scripts to a small service that other tools and dashboards can call safely
- Common pitfall: If route handlers own all the business logic, testing and refactoring become harder quickly.
- Interview lens: Interviewers like thin-route answers because they reflect maintainable boundary design.
from fastapi import FastAPI
app = FastAPI()
@app.get("/orders/{order_id}")
def get_order(order_id: str) -> dict[str, str]:
return {"order_id": order_id}
Request Models Make Input Contracts Visible Before Business Logic Runs
Use typed request models so incoming payload shape and validation expectations are obvious at the boundary.
In OrderOps, OrderOps is graduating from internal scripts to a small service that other tools and dashboards can call safely. That makes Pydantic Models a real engineering concern instead of a trivia topic. It affects whether the script or service stays easy to trust when another engineer reads it six weeks later.
The common failure mode is straightforward: Raw dictionaries at the web boundary make contract drift and validation gaps more likely. The stronger move is to make the rule explicit, keep the data shape visible, and leave a code path that is easy to narrate under interview pressure. Strong candidates explain models as contracts, not only as convenience.
- Use typed request models so incoming payload shape and validation expectations are obvious at the boundary.
- Project lens: OrderOps is graduating from internal scripts to a small service that other tools and dashboards can call safely
- Common pitfall: Raw dictionaries at the web boundary make contract drift and validation gaps more likely.
- Interview lens: Strong candidates explain models as contracts, not only as convenience.
from pydantic import BaseModel
class CreateOrderRequest(BaseModel):
sku: str
quantity: int
Validation Belongs At The Boundary Where Invalid Input First Becomes Visible
Reject bad requests before the workflow invests more state and side effects into them.
In OrderOps, OrderOps is graduating from internal scripts to a small service that other tools and dashboards can call safely. That makes Validation a real engineering concern instead of a trivia topic. It affects whether the script or service stays easy to trust when another engineer reads it six weeks later.
The common failure mode is straightforward: Allowing invalid data deeper into the service layer makes later failures harder to interpret. The stronger move is to make the rule explicit, keep the data shape visible, and leave a code path that is easy to narrate under interview pressure. Interviewers often reward explicit boundary validation because it keeps system layers honest.
- Reject bad requests before the workflow invests more state and side effects into them.
- Project lens: OrderOps is graduating from internal scripts to a small service that other tools and dashboards can call safely
- Common pitfall: Allowing invalid data deeper into the service layer makes later failures harder to interpret.
- Interview lens: Interviewers often reward explicit boundary validation because it keeps system layers honest.
class CreateOrderRequest(BaseModel):
sku: str
quantity: int
@field_validator("quantity")
@classmethod
def quantity_must_be_positive(cls, value: int) -> int:
if value <= 0:
raise ValueError("quantity must be positive")
return value
Dependencies Should Enter At Known Seams Instead Of Being Pulled In From Everywhere
Inject repositories and services at boundary seams so tests and reasoning stay local and explicit.
In OrderOps, OrderOps is graduating from internal scripts to a small service that other tools and dashboards can call safely. That makes Dependency Injection a real engineering concern instead of a trivia topic. It affects whether the script or service stays easy to trust when another engineer reads it six weeks later.
The common failure mode is straightforward: Global hidden dependencies make route behavior harder to predict and harder to override in tests. The stronger move is to make the rule explicit, keep the data shape visible, and leave a code path that is easy to narrate under interview pressure. Candidates who speak clearly about seams and ownership sound more maintainable.
- Inject repositories and services at boundary seams so tests and reasoning stay local and explicit.
- Project lens: OrderOps is graduating from internal scripts to a small service that other tools and dashboards can call safely
- Common pitfall: Global hidden dependencies make route behavior harder to predict and harder to override in tests.
- Interview lens: Candidates who speak clearly about seams and ownership sound more maintainable.
from fastapi import Depends
def get_repo() -> OrderRepository:
return OrderRepository()
@app.get("/orders")
def list_orders(repo: OrderRepository = Depends(get_repo)) -> list[dict]:
return repo.list_recent()
The Service Layer Should Own Workflow And Policy, Not Framework Details
Place business orchestration where it can be tested and explained without the web stack attached.
In OrderOps, OrderOps is graduating from internal scripts to a small service that other tools and dashboards can call safely. That makes Service Layer a real engineering concern instead of a trivia topic. It affects whether the script or service stays easy to trust when another engineer reads it six weeks later.
The common failure mode is straightforward: If workflow decisions live in the route handler, the boundary and policy concerns collapse together. The stronger move is to make the rule explicit, keep the data shape visible, and leave a code path that is easy to narrate under interview pressure. This is a common interview distinction because it reveals whether you think in layers.
- Place business orchestration where it can be tested and explained without the web stack attached.
- Project lens: OrderOps is graduating from internal scripts to a small service that other tools and dashboards can call safely
- Common pitfall: If workflow decisions live in the route handler, the boundary and policy concerns collapse together.
- Interview lens: This is a common interview distinction because it reveals whether you think in layers.
class OrderService:
def __init__(self, repo: OrderRepository) -> None:
self._repo = repo
Persistence Concerns Should Stay Beneath The Service Layer
Let the service coordinate workflow while the repository handles data access contracts and queries.
In OrderOps, OrderOps is graduating from internal scripts to a small service that other tools and dashboards can call safely. That makes Repository Boundaries a real engineering concern instead of a trivia topic. It affects whether the script or service stays easy to trust when another engineer reads it six weeks later.
The common failure mode is straightforward: Leaking database detail into handlers or domain logic makes every change ripple farther than necessary. The stronger move is to make the rule explicit, keep the data shape visible, and leave a code path that is easy to narrate under interview pressure. Interviewers often hear this as a sign that you have worked in maintainable backend codebases.
- Let the service coordinate workflow while the repository handles data access contracts and queries.
- Project lens: OrderOps is graduating from internal scripts to a small service that other tools and dashboards can call safely
- Common pitfall: Leaking database detail into handlers or domain logic makes every change ripple farther than necessary.
- Interview lens: Interviewers often hear this as a sign that you have worked in maintainable backend codebases.
@app.post("/orders")
def create_order(request: CreateOrderRequest, service: OrderService = Depends(get_service)):
return service.create(request)
API Errors Should Match The Boundary Contract Instead Of Leaking Internal Confusion
Return status codes and messages that are truthful for the caller while preserving deeper detail in logs.
In OrderOps, OrderOps is graduating from internal scripts to a small service that other tools and dashboards can call safely. That makes API Errors a real engineering concern instead of a trivia topic. It affects whether the script or service stays easy to trust when another engineer reads it six weeks later.
The common failure mode is straightforward: Dumping raw exceptions at the client boundary makes the API feel accidental and insecure. The stronger move is to make the rule explicit, keep the data shape visible, and leave a code path that is easy to narrate under interview pressure. Good interview answers distinguish caller-facing errors from operator-facing diagnostics.
- Return status codes and messages that are truthful for the caller while preserving deeper detail in logs.
- Project lens: OrderOps is graduating from internal scripts to a small service that other tools and dashboards can call safely
- Common pitfall: Dumping raw exceptions at the client boundary makes the API feel accidental and insecure.
- Interview lens: Good interview answers distinguish caller-facing errors from operator-facing diagnostics.
from fastapi import HTTPException
if not order:
raise HTTPException(status_code=404, detail="order not found")
A Service Boundary Is Only Real If You Can Verify The Contract At The HTTP Layer
Use boundary tests to prove routing, validation, status codes, and serialization rather than trusting the framework blindly.
In OrderOps, OrderOps is graduating from internal scripts to a small service that other tools and dashboards can call safely. That makes API Contract Testing a real engineering concern instead of a trivia topic. It affects whether the script or service stays easy to trust when another engineer reads it six weeks later.
The common failure mode is straightforward: Assuming the framework will do the right thing without verification leaves the contract under-proven. The stronger move is to make the rule explicit, keep the data shape visible, and leave a code path that is easy to narrate under interview pressure. Interviewers like candidates who know which parts of the stack deserve their own testing seam.
- Use boundary tests to prove routing, validation, status codes, and serialization rather than trusting the framework blindly.
- Project lens: OrderOps is graduating from internal scripts to a small service that other tools and dashboards can call safely
- Common pitfall: Assuming the framework will do the right thing without verification leaves the contract under-proven.
- Interview lens: Interviewers like candidates who know which parts of the stack deserve their own testing seam.
from fastapi.testclient import TestClient
client = TestClient(app)
response = client.get("/orders/ORD-1")
assert response.status_code == 200
Chapter Milestone And Interview Checkpoint
The milestone for this chapter is clear: design a small FastAPI service with typed requests, clear validation, service orchestration, and boundary-aware error handling
That milestone matters because interview prep is not only about remembering Python features. It is about explaining why the code is shaped that way, what bug or maintenance cost the shape avoids, and what you would test before calling the work safe.
This chapter should end with two kinds of confidence. First, you should be able to write and read the code in context. Second, you should be able to explain the tradeoff behind it in plain engineering language.
- Milestone: design a small FastAPI service with typed requests, clear validation, service orchestration, and boundary-aware error handling
- Healthy interview answers explain both code behavior and design intent.
- Good preparation means being able to trace a small example without guessing.
- Bridge to next chapter: the next chapter strengthens Python language fluency further with decorators and context managers so infrastructure patterns stay readable
Chapter takeaway
A good API layer makes request contracts explicit, keeps business workflow out of route handlers, and returns errors that match the boundary honestly.