Why Some Validation Belongs in the Application Layer
- Published on
Junyoung Yang
While implementing a gift API in Kakao Tech Campus, I kept thinking about where validation should happen. It was not just about whether the data could be saved to the database. I also had to decide which part of the system should check each rule.
This came up often in APIs that had to make decisions based on existing data, such as updating a product or adding a product to a wishlist.
At first, it seemed reasonable to leave many things to database constraints. A unique constraint or foreign key constraint will eventually block invalid data. But when I thought about what error to return to the user, and how to make the business rule visible in code, relying only on database errors did not feel enough.
This post is a record of how I thought about the boundary between database constraints and application-level validation.
Problem
When building the product update API, I first had to check whether the target product existed.
There were two possible approaches.
The first approach was to run the update query directly and treat an affected row count of 0 as a non-existing product. This has the benefit of reducing one query.
The second approach was to look up the product first, and then update it if it exists. This can add one more query, but the flow is easier to understand.
At first, direct update looked better from a performance point of view. But from an API point of view, I had to distinguish between a request trying to update a non-existing product and a request where the update succeeded but the values did not really change.
So I did not look only at the number of queries. I also checked whether the approach could express the meaning of the error clearly enough.
Cause Check
I had a similar concern when implementing the wishlist feature.
A user should not be able to add the same product twice, and they should not be able to add a product that does not exist.
In this case, I could rely on a unique constraint or a foreign key constraint and catch the database exception. But if I did that, the application code would not show very clearly which rule was broken.
When I checked the rules in the application first, the flow became easier to read.
- Check whether the requested product exists.
- Check whether the product is already in the wishlist.
- Return a domain exception that matches the case.
This could add a few queries, but the API response and the domain rule became easier to understand.
Approach
Database constraints are still needed as the last line of defense. But if every validation rule is pushed down to the database, the intention can become hidden in the application code.
This matters especially when the API needs to return an error code or message that the user can understand. In those cases, it was better to check the domain rule in the application first instead of interpreting raw database exceptions afterward.
The points I checked were:
- Is this validation a business rule?
- Should the user receive a meaningful error for this case?
- Does the database exception explain the problem clearly enough?
- Is the performance cost acceptable for the current requirement?
Looking up data first is not always the right answer. But when the meaning of the user request needs to be checked clearly, application-level validation felt like the better choice.
Implementation Check
Adding validation can increase the number of queries. So I do not think checking everything in the application first is always the best approach.
In this project, though, I chose readable error handling and a clear code flow over saving one additional lookup. Considering the expected traffic and the type of API, that cost was acceptable.
If the same logic were in a batch job or a performance-sensitive path, I might have chosen differently. I did not want to treat either database constraints or application validation as the only correct answer. I had to decide based on the purpose of the API and the meaning of the failure response.
Checkpoints
After this work, I started checking these points more often when deciding where validation should happen.
- Keep database constraints as the final safety net.
- Make business rules visible in the application code when possible.
- Validate directly when the user needs a meaningful error response.
- Measure and decide separately when the performance cost is high.
Validation is not only about blocking invalid input. It also shows what kind of requests the API accepts and how it interprets them.
Takeaway
This experience helped me separate two ideas: the database can block invalid data, but that does not always mean the application should skip the check.
Database constraints are still necessary. But interpreting a user request according to domain rules and returning a clear failure reason is also something the application should care about.