跪拜 Guibai
← All articles
Frontend · Backend · Interview

A Single Subtitle Field Touches Every Layer of a Backend

By swipe ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

A field that takes five minutes to add on the frontend exposes every structural decision on the backend: schema defaults, validation boundaries, authorization rules, and cache-invalidation contracts. Missing any one of them produces a bug that looks like “the API works” but silently serves stale data or lets unauthorized callers through.

Summary

A frontend developer adding a subtitle input might see one form field and one API call. On the backend, that same field must be threaded through the SQL schema, the persistence entity, a validated request DTO, a response DTO, a facade contract, a controller endpoint, path-based security rules, and a cache eviction step. Each layer answers a different question: where the data lives, who can write it, what makes it valid, and how stale caches get cleared. The `fullstack-mall` codebase demonstrates this with a real `PATCH /api/admin/products/{id}/subtitle` endpoint, backed by `ProductSubtitleUpdateRequest`, `ProductFacade`, `ProductDbService`, and `ProductDetailCacheService`.

The walkthrough treats the field as a flow, not a feature. It starts at the database column — `subtitle VARCHAR(100) NOT NULL DEFAULT ''` — and moves outward through the entity, the DTOs, the facade’s trim-and-update logic, the controller’s thin HTTP handling, and the security config that gates `/api/admin/**` to the ADMIN role. A curl-driven verification section exercises the happy path, validation failures, 401/403 responses, and the 404 when a product doesn’t exist, then shows how to confirm cache eviction via Redis CLI.

Common mistakes — forgetting `@Valid`, placing a write endpoint on a public controller, skipping the response DTO, or rolling back a MySQL write because Redis deletion failed — are catalogued with their fixes. Appendices turn the lesson into reusable checklists for any field-level change, covering persistence decisions, input ownership, visibility, authorization, cache impact, and error contracts.

Takeaways
Every persistent field must be added to the SQL schema, the entity, the request DTO, and the response DTO; skipping any layer breaks the contract.
Bean Validation annotations (`@NotBlank`, `@Size`) on the request DTO are the backend’s non-negotiable input gate — frontend validation is a UX convenience, not a security control.
Write endpoints belong under `/api/admin/**` so that `SecurityConfig` path rules enforce the ADMIN role without per-method checks.
After a write that changes cached data, the corresponding Redis key must be evicted; the project deliberately logs and swallows Redis failures instead of rolling back a successful MySQL update.
`ProductDbService.updateSubtitle` checks the UPDATE affected-row count to distinguish a successful write from a non-existent product, returning a `PRODUCT_NOT_FOUND` business exception when zero rows change.
The facade layer handles input normalization (`trim()`) and orchestrates the DB update, cache eviction, and response assembly, keeping the controller thin.
A field’s flow — DB column → entity → request/response DTO → facade → controller → security → cache — is a reusable mental model for any backend change.
Conclusions

Frontend-to-backend transitions often underestimate field additions because the frontend cost is trivial; the real cost is maintaining consistency across six or more backend layers.

The project’s decision to let Redis eviction failures degrade gracefully (log and continue) rather than roll back a committed MySQL transaction reflects a mature fail-open posture for the caching tier.

Using `LambdaUpdateWrapper` with an explicit `affectedRows` check turns a simple UPDATE into a precise existence gate, which is the same pattern needed for inventory, order-state transitions, and payment captures.

Path-based authorization (`/api/admin/**` → ADMIN) removes the need for repetitive role checks in controller methods, but it also means a misplaced endpoint on a public controller becomes an instant security hole.

The checklist in the appendices generalizes the subtitle exercise into a decision tree — persist or not, user-filled or server-generated, public or admin-visible, cache-affecting or not — that applies to nearly every field-level story.

Concepts & terms
Request DTO
A dedicated Java object (often annotated with `@Data` and validation constraints) that defines the shape and rules of an incoming JSON request body, keeping controllers free of raw maps or loose string parameters.
Bean Validation (`@Valid`, `@NotBlank`, `@Size`)
A Java standard that automatically validates annotated fields when a controller parameter is marked `@Valid`. Failures throw `MethodArgumentNotValidException`, which a global exception handler can translate into structured error responses.
Facade pattern (in this project)
A business-orchestration layer that sits between the controller and data-access services. It handles input normalization, calls the database service, triggers cache eviction, and assembles the response DTO, keeping HTTP concerns out of business logic.
Cache eviction after write (Cache-Aside)
The practice of deleting a cached entry immediately after the underlying database row is updated, so the next read falls through to the database and repopulates the cache with fresh data.
Affected-row check for existence
Using the return value of a SQL UPDATE (the count of rows modified) to determine whether the target row existed. A zero count means the entity was not found, allowing the service to throw a specific business exception like `PRODUCT_NOT_FOUND`.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗