跪拜 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 ↗