Enums, Exceptions, and Robust Error Handling
Validation and Error-Handling Strategies
You are viewing a free preview lesson.
Lesson Overview
Validation-এর উদ্দেশ্য শুধু invalid input detect করা নয়।
একটি strong validation design নিশ্চিত করে:
- Invalid data system-এর গভীরে না যায়
- Domain object invalid state-এ তৈরি না হয়
- Caller clear failure reason পায়
- একই rule unnecessaryভাবে বিভিন্ন জায়গায় duplicate না হয়
- Internal exception সরাসরি user-এর কাছে expose না হয়
- Expected validation errors এবং technical failures আলাদা থাকে
Real application-এ validation বিভিন্ন layer-এ হতে পারে:
User interface
API request
Application service
Domain object
Database
External integration
সব layer একই rule enforce করবে না।
এই lesson-এ আমরা শিখব:
- Validation কী
- Fail-fast validation
- Request validation
- Constructor validation
- Domain invariant validation
- Application-service validation
- Database constraints
- Exception vs validation result
- Single error vs multiple errors
- Boundary error translation
- Consistent error response
- Duplicated validation avoid করা
- Security এবং authorization validation-এর distinction
Learning Objectives
এই lesson শেষে আপনি পারবেন:
- Different validation responsibilities identify করতে
- Validation appropriate layer-এ রাখতে
- Constructor এবং method contracts enforce করতে
- Domain invariants protect করতে
- Expected field errors collect করতে
- Exception এবং validation result-এর মধ্যে choose করতে
- Internal failuresকে safe external responses-এ translate করতে
- Duplicate validation consciously manage করতে
- Database constraints এবং domain validation combine করতে
What Is Validation?
Validation checks whether data or an operation satisfies required rules।
Examples:
Course title is required
Price cannot be negative
Course code must be unique
Published course must contain lessons
Learner ID must be positive
User must have permission to publish
সব validation rule একই ধরনের নয়।
Useful categories:
Input format validation
Domain invariant validation
Business rule validation
Authorization validation
Persistence constraint validation
Infrastructure validation
Validation Is Not Only About Strings
Validation may inspect:
- Null values
- Blank text
- Numeric ranges
- Collection size
- Duplicate values
- Current object state
- Relationships between fields
- Existing database records
- User permissions
- External resource availability
Example:
Discount end date must be after start date
এটি single field নয়, relationship validation।
Fail Fast
Fail fast means invalid data যত তাড়াতাড়ি detect করা সম্ভব, তত তাড়াতাড়ি reject করা।
Weak:
public Course(
String title
) {
this.title = title;
}
Failure later:
title.toUpperCase();
Better:
public Course(
String title
) {
if (
title == null
|| title.isBlank()
) {
throw new IllegalArgumentException(
"Course title is required."
);
}
this.title =
title.strip();
}
Invalid Course object তৈরি হয় না।
Fail Fast Does Not Mean Validate Everything Everywhere
Weak approach:
UI validates title
Controller validates title
Service validates title
Domain validates title
Repository validates title
Some duplication may be justified, but blindly repeating every rule creates maintenance problems।
Instead ask:
Which layer owns this rule?
Which layers need early feedback?
Which layers are trust boundaries?
Validation Layers
A useful model:
Request boundary
↓
Application service
↓
Domain object
↓
Persistence boundary
Each layer has different responsibility।
Request Validation
Request validation checks whether incoming data can be understood and accepted structurally।
Examples:
Required JSON field missing
Price text is not numeric
Course code format invalid
Page size exceeds API limit
Unknown enum value
Request layer may validate:
- Presence
- Basic format
- Length
- Type conversion
- Simple range
- Supported values
Example request model:
public record CreateCourseRequest(
String code,
String title,
Long priceInPaisa
) {
}
Boundary validation:
if (
request.title() == null
|| request.title().isBlank()
) {
return badRequest(
"COURSE_TITLE_REQUIRED",
"Course title is required."
);
}
Why Validate at the Request Boundary?
Benefits:
- Client receives quick feedback
- Invalid data does not enter deeper layers
- Transport-specific error details can be returned
- Parsing failures are handled close to the input
But request validation alone is not enough।
Domain objects may also be created from:
- Background jobs
- Tests
- Message consumers
- Internal services
- Database restoration
- Command-line tools
Domain rules must not depend entirely on one controller।
Constructor Validation
Constructor validation protects object creation।
public final class CourseCode {
private final String value;
public CourseCode(
String value
) {
if (
value == null
|| value.isBlank()
) {
throw new IllegalArgumentException(
"Course code is required."
);
}
String normalized =
value.strip()
.toUpperCase();
if (
!normalized.matches(
"[A-Z0-9-]+"
)
) {
throw new IllegalArgumentException(
"Course code contains unsupported characters."
);
}
this.value =
normalized;
}
}
Any CourseCode instance is guaranteed valid।
Constructor Validation Should Protect Invariants
An invariant is a rule that must always remain true for a valid object।
Examples:
Course code is non-blank
Price is non-negative
Lesson ID is positive
Enrollment has learner and course
Constructor should reject any input that would create an invalid object।
Method Validation
Methods should validate:
- Arguments
- Current state
- Operation rules
Example:
public void addLesson(
Lesson lesson
) {
if (lesson == null) {
throw new IllegalArgumentException(
"Lesson is required."
);
}
if (
status
!= CourseStatus.DRAFT
) {
throw new IllegalStateException(
"Lessons can be added only to draft courses."
);
}
if (
containsLessonId(
lesson.getId()
)
) {
throw new DuplicateLessonException(
lesson.getId()
);
}
lessons.add(
lesson
);
}
Different failures use different categories:
Null argument → IllegalArgumentException
Invalid state → IllegalStateException
Domain duplicate → DuplicateLessonException
Application-Service Validation
Some rules require data outside one object।
Examples:
Course code must be unique across catalog
Learner must exist
Course must be purchasable
Instructor must own the course
Enrollment must not already exist
A Course object alone cannot check the entire database।
Application service can coordinate:
public void createCourse(
CourseCode code,
String title
) {
if (
repository.existsByCode(
code
)
) {
throw new DuplicateCourseCodeException(
code
);
}
Course course =
new Course(
code,
title
);
repository.save(
course
);
}
Domain Validation vs Application Validation
Domain Object
Checks rules that depend on its own state:
Course cannot publish without lessons
Price cannot be negative
Duplicate lesson ID forbidden
Application Service
Checks rules involving external data or multiple aggregates:
Course code unique across repository
Learner exists
Instructor can modify this course
Keeping this distinction prevents domain objects from depending directly on repositories or external services।
Authorization Is Not Ordinary Validation
Example:
User is not allowed to publish this course
This is an authorization failure, not simply invalid data।
Do not report:
Course ID is invalid
when the actual issue is permission।
Possible exception:
AccessDeniedException
or application-specific:
CourseAccessDeniedException
Authorization failures may map to:
HTTP 403 Forbidden
while invalid input maps to:
HTTP 400 Bad Request
Authentication vs Authorization
Authentication
Who is the user?
Failure may mean:
Not logged in
Invalid token
Expired session
Authorization
Is this user allowed to perform this action?
Failure may mean:
Authenticated but lacks permission
These should not be mixed with domain field validation।
Database Constraints
Database-level constraints protect persisted data।
Examples:
NOT NULL
UNIQUE
CHECK
FOREIGN KEY
Possible schema rules:
course.code UNIQUE
course.price_in_paisa >= 0
lesson.course_id FOREIGN KEY
Database constraints are important because concurrent requests can bypass application-level assumptions।
Object Validation Does Not Replace Database Constraints
Two requests may both check:
Course code does not exist
Then both attempt insert।
Without database uniqueness:
Duplicate course code may be stored
Therefore important invariant may need:
- Application validation for clear early feedback
- Database constraint for final consistency
Database Constraint Does Not Replace Domain Validation
Relying only on database:
repository.save(
invalidCourse
);
means failure occurs late and may expose technical errors।
Domain validation provides:
- Clearer messages
- Earlier failure
- Valid in-memory objects
- Easier testing
Both layers can be complementary।
Avoid Unnecessary Validation Duplication
Suppose CourseCode constructor guarantees:
Non-blank normalized code
Then every service does not need to re-check:
courseCode.getValue().isBlank()
Receiving a valid CourseCode type already communicates validation।
Strong types reduce repeated validation।
Parse at the Boundary, Use Strong Types Internally
Boundary receives:
String rawCourseCode
Create:
CourseCode courseCode =
new CourseCode(
rawCourseCode
);
Internal methods accept:
CourseCode
rather than:
String
This avoids repeatedly normalizing and validating raw strings।
Single Error Fail-Fast Strategy
Constructor example:
if (
title == null
|| title.isBlank()
) {
throw new IllegalArgumentException(
"Course title is required."
);
}
if (priceInPaisa < 0) {
throw new IllegalArgumentException(
"Price cannot be negative."
);
}
Only the first detected error is reported।
This is suitable when:
- Internal code calls the method
- Invalid state should stop immediately
- Caller should fix one contract violation
- Object must not be created partially
Collecting Multiple Validation Errors
Interactive forms often benefit from returning all field errors at once।
Weak user experience:
Submit → Title error
Submit again → Price error
Submit again → Code error
Better:
Course title is required
Price cannot be negative
Course code format is invalid
A validation result can collect them।
Validation Error Model
public record ValidationError(
String field,
String code,
String message
) {
public ValidationError {
if (
field == null
|| field.isBlank()
) {
throw new IllegalArgumentException(
"Validation field is required."
);
}
if (
code == null
|| code.isBlank()
) {
throw new IllegalArgumentException(
"Validation code is required."
);
}
if (
message == null
|| message.isBlank()
) {
throw new IllegalArgumentException(
"Validation message is required."
);
}
}
}
Validation Result
import java.util.ArrayList;
import java.util.List;
public final class ValidationResult {
private final List<ValidationError> errors;
public ValidationResult() {
this.errors =
new ArrayList<>();
}
public void addError(
String field,
String code,
String message
) {
errors.add(
new ValidationError(
field,
code,
message
)
);
}
public boolean isValid() {
return errors.isEmpty();
}
public List<ValidationError> getErrors() {
return List.copyOf(
errors
);
}
}
Request Validator Example
public final class CreateCourseRequestValidator {
public ValidationResult validate(
CreateCourseRequest request
) {
ValidationResult result =
new ValidationResult();
if (request == null) {
result.addError(
"request",
"REQUEST_REQUIRED",
"Request is required."
);
return result;
}
if (
request.code() == null
|| request.code().isBlank()
) {
result.addError(
"code",
"COURSE_CODE_REQUIRED",
"Course code is required."
);
}
if (
request.title() == null
|| request.title().isBlank()
) {
result.addError(
"title",
"COURSE_TITLE_REQUIRED",
"Course title is required."
);
}
if (
request.priceInPaisa() == null
) {
result.addError(
"priceInPaisa",
"COURSE_PRICE_REQUIRED",
"Course price is required."
);
} else if (
request.priceInPaisa() < 0
) {
result.addError(
"priceInPaisa",
"COURSE_PRICE_NEGATIVE",
"Course price cannot be negative."
);
}
return result;
}
}
Why Use a Validation Result Here?
Request validation is expected।
Users commonly make multiple field mistakes।
No exceptional system failure occurred।
Therefore a structured result is often better than throwing one exception per field।
Converting Validated Input to Domain Types
After request validation:
ValidationResult validation =
validator.validate(
request
);
if (!validation.isValid()) {
return validationResponse(
validation.getErrors()
);
}
Then create strong types:
CourseCode courseCode =
new CourseCode(
request.code()
);
Course course =
new Course(
courseCode,
request.title(),
request.priceInPaisa()
);
Domain constructor still validates its invariants।
Why both?
- Boundary provides user-friendly multiple errors
- Domain protects itself from every caller
This duplication is deliberate and serves different purposes।
Deliberate Validation Duplication
Some duplicate checks are healthy।
Example:
Request validator checks blank title
Course constructor also checks blank title
Reasons differ:
Request layer → User-friendly field error
Domain layer → Invalid object can never exist
This is acceptable।
Unhealthy duplication occurs when the same complex business rule is independently reimplemented in several places and can drift।
Centralize Complex Rules
Weak:
Controller calculates publication eligibility
Service calculates publication eligibility
Course calculates publication eligibility
Better:
course.publish();
The course owns its publication invariant।
Boundary catches or translates the result।
Exception vs Validation Result
Use a validation result when:
- Invalid input is expected
- Multiple errors should be returned together
- Caller naturally displays field errors
- Validation does not represent technical failure
Use an exception when:
- A method contract is violated
- Invalid object state would be created
- Operation cannot fulfill its contract
- Failure should propagate to a boundary
- Technical dependency fails
Example: Blank Title
Request Boundary
Return:
ValidationError
because user can correct the field।
Domain Constructor
Throw:
IllegalArgumentException
because blank title violates object creation contract।
Same rule, different handling purpose।
Example: Duplicate Course Code
Possible approaches:
Pre-Check Result
if (
repository.existsByCode(
code
)
) {
return CreateCourseResult.DUPLICATE_CODE;
}
Domain/Application Exception
throw new DuplicateCourseCodeException(
code
);
Database Constraint Translation
Catch uniqueness violation and translate to:
DuplicateCourseCodeException
In concurrent systems, database constraint remains the final authority।
Race Condition in Pre-Validation
if (
!repository.existsByCode(
code
)
) {
repository.save(
course
);
}
Between check and save, another request may insert the same code।
Therefore:
Pre-check improves feedback
Database UNIQUE constraint guarantees consistency
Both may be needed।
Boundary Translation
An application boundary converts internal failures into external responses।
Possible mapping:
IllegalArgumentException → 400 Bad Request
ValidationResult errors → 400 Bad Request
Authentication failure → 401 Unauthorized
Access denied → 403 Forbidden
CourseNotFoundException → 404 Not Found
DuplicateCourseCodeException → 409 Conflict
CourseRepositoryException → 500 Internal Server Error
The exact mapping depends on API conventions।
Consistent Error Response
Example response model:
import java.util.List;
public record ErrorResponse(
String code,
String message,
List<FieldError> fieldErrors
) {
public ErrorResponse {
fieldErrors =
fieldErrors == null
? List.of()
: List.copyOf(
fieldErrors
);
}
}
Field error:
public record FieldError(
String field,
String code,
String message
) {
}
Example Validation Response
{
"code": "VALIDATION_FAILED",
"message": "The request contains invalid fields.",
"fieldErrors": [
{
"field": "title",
"code": "COURSE_TITLE_REQUIRED",
"message": "Course title is required."
},
{
"field": "priceInPaisa",
"code": "COURSE_PRICE_NEGATIVE",
"message": "Course price cannot be negative."
}
]
}
Stable codes help clients make programmatic decisions।
Messages remain human-readable।
Do Not Use Exception Class Names as Public Codes
Weak API code:
java.lang.IllegalArgumentException
Better:
COURSE_TITLE_REQUIRED
DUPLICATE_COURSE_CODE
COURSE_NOT_FOUND
External codes should be stable application contracts।
Internal Message vs External Message
Internal exception:
Course repository query failed for code JAVA-OOP because connection pool timed out.
External response:
Course information is temporarily unavailable.
Boundary should protect internal details।
Avoid Returning Raw Validation Messages from Deep Layers
A domain message may be technically correct but not localized or user-friendly।
Domain:
Price must be greater than or equal to zero.
UI may need:
Enter a valid course price.
Stable error codes allow presentation layer to choose appropriate text।
Validation Codes
Use clear stable identifiers:
COURSE_TITLE_REQUIRED
COURSE_CODE_INVALID
COURSE_PRICE_NEGATIVE
COURSE_NOT_FOUND
ALREADY_ENROLLED
Avoid:
ERROR_1
INVALID
BAD_REQUEST
Codes should communicate the specific condition।
Validation and Localization
Do not rely only on English exception messages for UI。
Possible design:
Code: COURSE_TITLE_REQUIRED
Default message: Course title is required.
Frontend can translate code to Bangla or another language।
Backend may still provide a safe fallback message।
Security Validation
Never trust external input শুধু client-side validation হয়েছে বলে।
Browser validation can be bypassed।
Server must validate:
- IDs
- Amounts
- Uploaded files
- Permissions
- Ownership
- Allowed status transitions
- Input lengths
- Supported formats
Client validation improves experience।
Server validation protects the system।
Validation Is Not Sanitization
Validation asks:
Is this input allowed?
Normalization asks:
How should equivalent input be represented?
Escaping or encoding asks:
How should data be safely used in a specific output context?
Example:
title.strip()
is normalization।
Checking length is validation।
HTML escaping is output-context protection।
Do not mix these concepts।
Normalize Before Comparing
Course code input:
java-oop
JAVA-OOP
Java-Oop
Normalize:
value.strip()
.toUpperCase()
Then validate and compare।
Centralize normalization in:
CourseCode
so all callers use the same rules।
Validation Order
A sensible order:
- Null/presence
- Basic format
- Range or length
- Relationship between fields
- Domain rules
- External uniqueness or existence
- Authorization
Example:
Check course code exists syntactically
before querying repository for that code
Avoid expensive checks for obviously invalid input।
Short-Circuit Expensive Validation
Weak:
boolean exists =
repository.existsByCode(
new CourseCode(
rawCode
)
);
when rawCode may be blank।
Better:
- Validate raw input
- Create
CourseCode - Query repository
This avoids unnecessary external calls and unclear exceptions।
Validation and Side Effects
Validate before state mutation when possible।
Weak:
course.setTitle(
title
);
if (
price < 0
) {
throw new IllegalArgumentException(
"Invalid price."
);
}
Partial mutation occurred before failure।
Better:
validateTitle(
title
);
validatePrice(
price
);
course.update(
title,
price
);
Atomic Validation and Mutation
For batch operations:
public void addLessons(
List<Lesson> newLessons
)
Validate all items first।
Then mutate।
Otherwise:
First lessons added
Later item invalid
Operation fails partially
Validation-first improves atomic behavior inside the object।
Validation of Collections
Examples:
if (lessons == null) {
throw new IllegalArgumentException(
"Lessons are required."
);
}
if (lessons.isEmpty()) {
throw new IllegalArgumentException(
"At least one lesson is required."
);
}
for (
Lesson lesson
: lessons
) {
if (lesson == null) {
throw new IllegalArgumentException(
"Lessons cannot contain null."
);
}
}
Also consider:
- Duplicate elements
- Maximum size
- Ordering
- Cross-item conflicts
Reusable Generic Validation Helpers
Simple helpers can reduce repetition।
public final class Validation {
private Validation() {
}
public static String requireText(
String value,
String fieldName
) {
if (
value == null
|| value.isBlank()
) {
throw new IllegalArgumentException(
fieldName
+ " is required."
);
}
return value.strip();
}
public static long requirePositive(
long value,
String fieldName
) {
if (value <= 0) {
throw new IllegalArgumentException(
fieldName
+ " must be positive."
);
}
return value;
}
}
Usage:
this.title =
Validation.requireText(
title,
"Course title"
);
Avoid Over-General Validation Frameworks
Weak helper:
validate(
value,
ruleName,
errorCode,
message,
formatter,
converter,
strategy
);
For simple domain code, this may become harder to read than direct validation।
Use abstractions only when they remove real duplication without hiding intent।
Complete Example: Course Creation Flow
CreateCourseRequest.java
package io.liveklass.course;
public record CreateCourseRequest(
String code,
String title,
Long priceInPaisa
) {
}
ValidationError.java
package io.liveklass.validation;
public record ValidationError(
String field,
String code,
String message
) {
}
ValidationResult.java
package io.liveklass.validation;
import java.util.ArrayList;
import java.util.List;
public final class ValidationResult {
private final List<ValidationError> errors;
public ValidationResult() {
this.errors =
new ArrayList<>();
}
public void add(
String field,
String code,
String message
) {
errors.add(
new ValidationError(
field,
code,
message
)
);
}
public boolean isValid() {
return errors.isEmpty();
}
public List<ValidationError> getErrors() {
return List.copyOf(
errors
);
}
}
CreateCourseRequestValidator.java
package io.liveklass.course;
import io.liveklass.validation.ValidationResult;
public final class CreateCourseRequestValidator {
public ValidationResult validate(
CreateCourseRequest request
) {
ValidationResult result =
new ValidationResult();
if (request == null) {
result.add(
"request",
"REQUEST_REQUIRED",
"Request is required."
);
return result;
}
if (
request.code() == null
|| request.code().isBlank()
) {
result.add(
"code",
"COURSE_CODE_REQUIRED",
"Course code is required."
);
}
if (
request.title() == null
|| request.title().isBlank()
) {
result.add(
"title",
"COURSE_TITLE_REQUIRED",
"Course title is required."
);
}
if (
request.priceInPaisa() == null
) {
result.add(
"priceInPaisa",
"COURSE_PRICE_REQUIRED",
"Course price is required."
);
} else if (
request.priceInPaisa() < 0
) {
result.add(
"priceInPaisa",
"COURSE_PRICE_NEGATIVE",
"Course price cannot be negative."
);
}
return result;
}
}
DuplicateCourseCodeException.java
package io.liveklass.course;
public final class DuplicateCourseCodeException
extends RuntimeException {
private final CourseCode courseCode;
public DuplicateCourseCodeException(
CourseCode courseCode
) {
super(
"Course code is already registered: "
+ courseCode
+ "."
);
this.courseCode =
courseCode;
}
public CourseCode getCourseCode() {
return courseCode;
}
}
CourseService.java
package io.liveklass.course;
public final class CourseService {
private final CourseRepository repository;
public CourseService(
CourseRepository repository
) {
if (repository == null) {
throw new IllegalArgumentException(
"Course repository is required."
);
}
this.repository =
repository;
}
public Course createCourse(
CourseCode code,
String title,
long priceInPaisa
) {
if (
repository.existsByCode(
code
)
) {
throw new DuplicateCourseCodeException(
code
);
}
Course course =
new Course(
code,
title,
priceInPaisa
);
repository.save(
course
);
return course;
}
}
Boundary Flow
public ErrorResponseOrCourse handle(
CreateCourseRequest request
) {
ValidationResult validation =
validator.validate(
request
);
if (!validation.isValid()) {
return validationFailure(
validation.getErrors()
);
}
try {
CourseCode code =
new CourseCode(
request.code()
);
Course course =
courseService.createCourse(
code,
request.title(),
request.priceInPaisa()
);
return success(
course
);
} catch (
IllegalArgumentException exception
) {
return badRequest(
"INVALID_COURSE_DATA",
exception.getMessage()
);
} catch (
DuplicateCourseCodeException exception
) {
return conflict(
"DUPLICATE_COURSE_CODE",
"A course with this code already exists."
);
} catch (
CourseRepositoryException exception
) {
logError(
exception
);
return serverError(
"COURSE_CREATION_FAILED",
"The course could not be created."
);
}
}
The exact response classes depend on the application framework।
The important part is separation:
Field validation
Domain construction
Business uniqueness
Infrastructure failure
Boundary translation
Common Mistakes
Relying Only on Frontend Validation
Requests can bypass the frontend।
Relying Only on Database Errors
Failures occur late and messages become technical।
Validating Only in Controllers
Other callers can create invalid domain objects।
Reimplementing Complex Domain Rules in Every Layer
Rules drift and become inconsistent।
Throwing One Exception per Form Field
Interactive validation becomes noisy and incomplete।
Returning Multiple Field Errors as One Vague Message
Client cannot associate errors with fields।
Using Raw Exception Messages as Stable Error Codes
Messages may change।
Returning Internal Stack Traces to Clients
Leaks implementation details।
Treating Authorization Failure as Invalid Input
Produces incorrect semantics and response status।
Performing External Checks Before Basic Validation
Wastes resources and may produce confusing failures।
Mutating State Before All Validation Completes
Creates partial updates।
Assuming Pre-Check Guarantees Uniqueness
Concurrent requests still require database constraints।
Over-Generalizing Validation Helpers
Hides domain intent and increases complexity।
Practice Exercises
Exercise 1: Request Validator
Create a validator for:
RegisterLearnerRequest
Fields:
name
email
age
Rules:
- Name required
- Email required and must contain
@ - Age must be at least 13
- Return all field errors together
Exercise 2: Domain Constructor
Create:
LearnerEmail
Requirements:
- Non-blank
- Strip whitespace
- Lowercase normalization
- Basic email format validation
- Immutable
Exercise 3: Layer Assignment
Choose the best layer for each rule:
- JSON field is missing
- Course price is negative
- Course code already exists globally
- User cannot edit another instructor’s course
- Database column must be unique
- Course cannot publish without lessons
Choose among:
Request boundary
Domain object
Application service
Authorization layer
Database
Exercise 4: Error Response
Design a response for:
Blank title
Negative price
Invalid course code
Include:
- Top-level code
- Safe message
- Field error list
- Stable error codes
Exercise 5: Validation Duplication
Explain why checking blank title in both:
Request validator
Course constructor
can be acceptable।
Then explain when duplicated validation becomes harmful।
Exercise 6: Race Condition
Explain why:
if (!repository.existsByCode(code)) {
repository.save(course);
}
does not guarantee uniqueness under concurrency।
Provide the additional database requirement।
Exercise 7: Result or Exception
Choose between validation result, built-in exception, custom exception, or infrastructure exception:
- User submits three invalid fields
- Internal code passes null
CourseCode - Course code already exists
- Database times out
- User lacks permission
- Course cannot publish from current state
Predict the Result
Question 1
Course course =
new Course(
null,
"Java",
0L
);
Assume constructor requires non-null CourseCode।
Answer
An exception should be thrown immediately।
Invalid Course object should not be created।
Question 2
A request validator finds:
Blank title
Negative price
Should it stop after the first field error?
Answer
Not necessarily।
For an interactive request, returning both field errors usually provides better feedback।
Question 3
An application checks that a course code is free, but another request inserts it before save।
Can application pre-validation alone prevent duplicates?
Answer
No।
A database UNIQUE constraint or equivalent atomic mechanism is still required।
Question 4
A domain constructor validates title, but a controller also validates it for field-level feedback।
Is this always harmful duplication?
Answer
No।
The controller improves user feedback; the constructor protects the domain invariant।
Question 5
Should an internal database host name be returned in an API error message?
Answer
No।
It should remain in secure internal logs or the exception cause chain।
Knowledge Check
Question 1
What is fail-fast validation?
Question 2
What does request validation protect?
Question 3
What does constructor validation protect?
Question 4
What belongs in application-service validation?
Question 5
Why are database constraints still necessary?
Question 6
When should multiple validation errors be collected?
Question 7
When is an exception better than a validation result?
Question 8
Why use stable error codes?
Question 9
What is deliberate validation duplication?
Question 10
Why should complex domain rules be centralized?
Question 11
What is the difference between authorization and validation?
Question 12
Why validate before external calls?
Question 13
Why validate before mutation?
Question 14
Why should internal exception details not be exposed directly?
Question 15
What is the benefit of strong domain types such as CourseCode?
Knowledge Check Answers
Answer 1
Invalid dataকে যত তাড়াতাড়ি সম্ভব reject করা, before it creates deeper invalid state।
Answer 2
Incoming transport data-এর presence, format, parsing, and field-level correctness।
Answer 3
It ensures invalid domain objects cannot be created।
Answer 4
Rules involving repositories, multiple aggregates, permissions, or external state।
Answer 5
Concurrent operations can bypass application pre-checks, so persistence must enforce final consistency।
Answer 6
When users can correct several expected field errors together, such as form submission।
Answer 7
When a method contract is violated, invalid state would exist, or the operation cannot complete due to technical failure।
Answer 8
Clients can make programmatic decisions without parsing changeable messages।
Answer 9
The same basic rule is checked at different layers for different purposes, such as user feedback and domain protection।
Answer 10
Multiple independent implementations can drift and produce inconsistent behavior।
Answer 11
Validation checks correctness; authorization checks whether the user is allowed to perform the action।
Answer 12
To avoid unnecessary expensive operations and confusing lower-level failures।
Answer 13
To prevent partial state changes when later validation fails।
Answer 14
They may contain stack traces, infrastructure details, or sensitive information।
Answer 15
They centralize normalization and invariants, reducing repeated raw-value validation।
Lesson Summary
এই lesson-এ আমরা শিখেছি:
- Validation বিভিন্ন layer-এ different responsibilities পালন করে
- Fail-fast invalid stateকে early reject করে
- Request validation field-level input feedback দেয়
- Constructor validation invalid domain objects prevent করে
- Method validation arguments এবং current state protect করে
- Application services cross-object এবং repository-dependent rules enforce করে
- Authorization ordinary field validation নয়
- Database constraints concurrent consistency protect করে
- Domain validation এবং database constraints complementary
- Strong types repeated raw validation reduce করে
- Single-error fail-fast internal contracts-এর জন্য useful
- Multiple validation errors interactive forms-এর জন্য useful
- Validation result expected field failures model করতে পারে
- Exceptions broken contracts এবং failed operations represent করতে পারে
- Deliberate validation duplication different purposes serve করতে পারে
- Complex rules one authoritative place-এ centralize করা উচিত
- Pre-validation uniqueness guarantee করে না
- Boundary internal failuresকে stable external responses-এ translate করে
- Stable error codes messages-এর চেয়ে stronger API contracts
- Internal diagnostic details usersকে expose করা উচিত নয়
- Client-side validation security boundary নয়
- Validation, normalization, and output escaping different concepts
- Expensive validation basic checks-এর পরে করা উচিত
- Mutation-এর আগে validation complete করা উচিত
- Collection and batch validation partial updates prevent করতে পারে
- Simple validation helpers useful, but over-generalization avoid করা উচিত
Next Lesson
পরবর্তী lesson:
Building Reliable Failure Boundaries
আমরা শিখব:
- Application boundaries
- API error translation
- Logging strategy
- Correlation IDs
- Safe user messages
- Retryable vs non-retryable failures
- Partial failure
- Idempotency
- Transaction boundaries
- Avoiding duplicate logging
- Designing predictable failure responses