Backend Engineering Masterclass
Follow one request from the edge to Java code, business rules, data, security and production operations.
The backend is the brain—and the trust boundary.
A frontend presents possibilities. The backend decides what is actually allowed, applies business rules, coordinates data and returns a stable result.

Accept
Receive requests from web, mobile, partners or other services.
Decide
Authenticate, authorize, validate and execute domain rules.
Coordinate
Read or change databases, caches, queues and external APIs.
Explain
Return a contract: status, headers and a structured body.
A language runs code; a framework supplies the common machinery.
| Ecosystem | Runtime / language | Typical frameworks | Common fit |
|---|---|---|---|
| Java | JVM / Java | Spring Boot, Quarkus | Large, long-lived enterprise systems |
| JavaScript | Node.js / TypeScript | Express, NestJS | Web APIs and full-stack teams |
| Python | Python | FastAPI, Django, Flask | APIs, automation and AI-heavy systems |
| .NET | CLR / C# | ASP.NET Core | Microsoft-centered enterprise systems |
Framework value
Routing, dependency injection, configuration, serialization, validation, security and database integration.
Node.js in one line
It brought the JavaScript engine outside the browser, allowing JavaScript to run server-side.
Selection rule
Prefer team competence, ecosystem maturity, workload fit and operability over fashion.
One HTTP request crosses several deliberate boundaries.

- The server accepts the connection and parses HTTP.
- Middleware attaches identity, correlation, limits and telemetry.
- The router selects a controller method.
- Input is deserialized and validated.
- The service executes the use case and business rules.
- The repository communicates with storage.
- The result is serialized into a status, headers and JSON body.
Separate transport, business decisions and persistence.

| Layer | Owns | Incident example | Avoid |
|---|---|---|---|
| Controller | HTTP input/output | POST /api/incidents → 201 | SQL and complex business rules |
| Service | Use cases and policies | Reject an invalid severity transition | HTTP-specific details |
| Repository | Persistence queries | Find incidents by status | Authorization decisions |
| Domain model | Business vocabulary and invariants | Incident, severity, status | Framework leakage where unnecessary |
@RestController
@RequestMapping("/api/incidents")
class IncidentController {
private final IncidentService service;
@GetMapping
List<Incident> list() { return service.findAll(); }
@PostMapping
ResponseEntity<Incident> create(@Valid @RequestBody CreateIncident request) {
return ResponseEntity.status(201).body(service.create(request));
}
@DeleteMapping("/{id}")
ResponseEntity<Void> delete(@PathVariable long id) {
service.delete(id);
return ResponseEntity.noContent().build();
}
}@Service
class IncidentService {
private final IncidentRepository repository;
Incident create(CreateIncident input) {
// Business policy belongs here, not in the browser.
if (input.severity() == Severity.P0 && input.owner() == null)
throw new ValidationException("P0 incidents require an owner");
return repository.save(Incident.from(input));
}
}Keep durable state outside replaceable application processes.
Database
Authoritative business records such as incidents and users.
Cache
Derived, temporary data that can usually be recreated.
Token or session store
Identity context carried by the client or shared across instances.
REST models resources through HTTP.
A good URL names a resource. The HTTP method expresses the operation. Status codes explain the outcome.
| Method | Meaning | Example | Typical success |
|---|---|---|---|
| GET | Read | GET /api/incidents/42 | 200 OK |
| POST | Create / command | POST /api/incidents | 201 Created |
| PUT | Replace | PUT /api/incidents/42 | 200 or 204 |
| PATCH | Partially update | PATCH /api/incidents/42 | 200 or 204 |
| DELETE | Remove | DELETE /api/incidents/42 | 204 No Content |
| Code | Meaning | Use |
|---|---|---|
| 400 | Bad Request | Malformed or invalid input |
| 401 | Unauthenticated | Identity is missing or invalid |
| 403 | Forbidden | Identity is known but not allowed |
| 404 | Not Found | Resource does not exist or is concealed |
| 409 | Conflict | State conflict or duplicate |
| 429 | Too Many Requests | Rate limit exceeded |
| 500 | Server Error | Unexpected internal failure |
An API is a contract, not a database exposed over HTTP.
Resource shape
Expose what consumers need. Use DTOs so internal tables and fields can evolve.
Query design
Filter, sort and paginate: ?status=OPEN&page=0&size=20.
Error shape
Return a stable code, safe message, field errors and correlation ID.
HTTP/1.1 201 Created
Location: /api/incidents/42
Content-Type: application/json
{
"id": 42,
"title": "Payment API latency",
"severity": "P1",
"status": "OPEN",
"createdAt": "2026-08-28T10:15:00Z"
}Choose the communication style that fits the boundary.

| Style | Best fit | Strength | Trade-off |
|---|---|---|---|
| REST + JSON | Public APIs, browsers, mobile | Simple, ubiquitous, inspectable | Larger payloads and looser contracts |
| gRPC + Protobuf | Internal service-to-service calls | Typed contracts, efficient binary transport | More tooling; browser use is less direct |
| Server-rendered HTML | Content-heavy or simpler applications | Server sends ready HTML | Different interaction model from a rich SPA |
Stable contracts let teams change independently.
OpenAPI
Machine-readable endpoints, schemas, authentication and responses; tools can render Swagger UI or generate clients.
Compatibility
Add optional fields safely. Avoid changing meaning, type or required behavior unexpectedly.
Versioning
Use a new version for breaking changes, and publish migration and retirement plans.
paths:
/api/incidents:
post:
summary: Create an incident
responses:
'201': { description: Created }
'400': { description: Invalid request }
'401': { description: Authentication required }API security is defense in depth.

Transport
HTTPS protects data in transit and verifies the server.
Identity
Authentication proves who is calling.
Permission
Authorization decides whether that principal may perform this action on this resource.
Input
Validate type, range, format, size and allowed values server-side.
http.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.GET, "/api/incidents/**").authenticated()
.requestMatchers(HttpMethod.DELETE, "/api/incidents/**").hasRole("ADMIN")
.anyRequest().denyAll());Sessions and tokens carry identity in different ways.
| Approach | How it works | Useful when | Watch for |
|---|---|---|---|
| Server session | Cookie references server-side state | Traditional web applications | Shared session storage and CSRF |
| Access token | Client sends a bearer token | APIs, mobile and distributed systems | Expiry, audience, storage and revocation |
| OAuth 2.0 | Delegated authorization flows | Applications accessing protected APIs | Choose the correct flow; OAuth is not itself login |
| OpenID Connect | Identity layer on OAuth 2.0 | Login and SSO | Validate issuer, audience, nonce and signatures |
Safe APIs survive abuse, retries and hostile input.
Rate limiting
Protect capacity with a per-user, API key or IP budget. Return 429 and retry guidance.
Idempotency
A repeated create request with the same idempotency key returns the original result instead of duplicating work.
Parameterized SQL
Keep code and values separate so input cannot become executable SQL.
// UNSAFE: input changes SQL meaning
String sql = "SELECT * FROM incidents WHERE title LIKE '%" + term + "%'";
// SAFE: the driver binds input as data
String sql = "SELECT * FROM incidents WHERE title LIKE ?";
jdbcTemplate.query(sql, mapper, "%" + term + "%");| Demo request | Expected result | Lesson |
|---|---|---|
Unsafe search: ' OR '1'='1 | Returns all rows | String concatenation changed the query |
| Safe search: same text | Returns no matches | The payload remained a value |
| 16+ rapid calls | 429 Too Many Requests | Capacity is bounded |
Scale by finding the bottleneck, not by guessing.

Vertical
Add CPU or memory to one machine. Simple, but it has an upper bound and larger failure impact.
Horizontal
Add instances behind a load balancer. Requires stateless processes and coordinated shared dependencies.
Measure
Track throughput, error rate, P50/P95 latency, saturation and dependency time.
Containers package; orchestration operates.
Docker image
Application, runtime and dependencies packaged into a repeatable artifact.
Container
A running, isolated process created from the image.
Kubernetes
Schedules replicas, provides service discovery, health checks, rolling releases and recovery.
FROM eclipse-temurin:21-jre
COPY target/incident-api.jar app.jar
USER 10001
ENTRYPOINT ["java", "-jar", "/app.jar"]A cache trades freshness and complexity for speed.
| Question | Design decision |
|---|---|
| What is cached? | Derived, expensive-to-read data—not every value |
| How long? | TTL based on acceptable staleness |
| What is the key? | Include tenant, permissions and relevant query dimensions |
| How is it invalidated? | Expire, update or delete after authoritative changes |
| What if Redis fails? | Fallback behavior must protect the database from a stampede |
A monolith and microservices are deployment choices—not maturity levels.

| Shape | Strength | Cost | Good starting point |
|---|---|---|---|
| Modular monolith | Simple deployment, calls and transactions | Shared release and scale boundary | Most new products and smaller teams |
| Microservices | Independent ownership, release and scaling | Network failures, eventual consistency, observability and platform burden | Clear bounded contexts with real independent needs |
Queues decouple time; events decouple ownership.
Queue
Work waits until a consumer is ready. Useful for load leveling and background jobs.
Event stream
Durable ordered facts can be consumed by multiple independent readers.
Delivery reality
Duplicates and retries happen. Consumers should be idempotent; poison messages need a dead-letter path.
Modernize legacy systems one controlled seam at a time.

- Discover: map dependencies, data ownership, change frequency, risk and business value.
- Stabilize: add tests, telemetry and reliable deployment around the current system.
- Wrap: create a stable API or event seam.
- Strangle: route one bounded capability to a new implementation.
- Migrate data: backfill, reconcile, shadow-read or dual-write with care, then cut over.
- Retire: remove old paths only after evidence, rollback planning and stakeholder sign-off.
Test the behavior and observe the production journey.
Unit test
Fast checks of service rules with dependencies replaced.
Integration test
Real framework, database, serialization and security boundaries.
Contract test
Confirms consumer and provider agree on the API shape.
End-to-end test
A few critical journeys across deployed components.
@Test
void p0RequiresOwner() {
var request = new CreateIncident("Database down", Severity.P0, null);
assertThrows(ValidationException.class, () -> service.create(request));
}| Signal | Question answered |
|---|---|
| Logs | What discrete event happened? |
| Metrics | How often and how much? |
| Traces | Where did one request spend time? |
| Audit records | Who performed which sensitive action? |
An AI capability is still a backend dependency.
Protect
Keep model keys server-side, authorize data retrieval and redact sensitive inputs.
Control
Set timeouts, budgets, model fallbacks and approval boundaries for side effects.
Observe
Measure quality, safety, latency, token cost, tool calls and business outcomes.
Complete incident API blueprint.
The same small service can demonstrate routing, layers, data, security, safe SQL, rate limits, documentation, tests and operations.
| Journey | Expected | Concept |
|---|---|---|
| GET /api/incidents | 200 + JSON list | Read endpoint and serialization |
| POST /api/incidents | 201 + created record | Validation, service rule and persistence |
| DELETE without credentials | 401 | Authentication required |
| DELETE as permitted admin | 204 | Authorization and empty success body |
| Unsafe injection payload | Incorrectly returns rows | Why concatenated SQL is dangerous |
| Safe injection payload | Empty result | Parameterized query |
| Rapid repeated traffic | 429 after limit | Rate limiting |
| Open Swagger UI | Interactive contract | OpenAPI documentation |
The full request in one sentence
Architecture review checklist
- Who is calling, and how is identity verified?
- Which action and record is that principal allowed to access?
- Where are inputs validated and business rules enforced?
- Which store owns the truth, and which data is cached or derived?
- What happens when a dependency is slow, unavailable or called twice?
- Can one request be traced without exposing confidential data?
- Can the system scale horizontally and deploy safely?
- Is the API contract documented and backward compatible?