Enums, Exceptions, and Robust Error Handling
Building Reliable Failure Boundaries
You are viewing a free preview lesson.
Lesson Overview
Domain object এবং application service failure detect করতে পারে।
কিন্তু end user বা API clientকে raw Java exception দেওয়া উচিত নয়।
Example internal failure:
CourseRepositoryException
Caused by: SQLException
Connection timed out
Client-এর প্রয়োজন হতে পারে:
{
"code": "COURSE_SERVICE_UNAVAILABLE",
"message": "Course information is temporarily unavailable."
}
যে layer internal failureকে external response-এ translate করে, সেটি একটি failure boundary।
Common boundaries:
HTTP API
Message consumer
Scheduled job
Command-line application
Background worker
User interface
একটি reliable boundary:
- Known failures classify করে
- Safe response তৈরি করে
- Unexpected failures log করে
- Stable error codes return করে
- Internal details hide করে
- Request correlation preserve করে
- Retryable এবং non-retryable failure distinguish করে
- Partial success নিয়ে deliberate decision নেয়
- Duplicate logging avoid করে
- Transaction এবং idempotency consider করে
Learning Objectives
এই lesson শেষে আপনি পারবেন:
- Failure boundary explain করতে
- Domain, validation, authorization, এবং infrastructure failures map করতে
- Consistent error response design করতে
- Safe user message এবং internal log আলাদা রাখতে
- Correlation ID-এর purpose বুঝতে
- Retryable failure identify করতে
- Partial failure handle করতে
- Idempotent operation design করতে
- Transaction boundary reason করতে
- Duplicate exception logging avoid করতে
- Unknown exception safely handle করতে
What Is an Application Boundary?
An application boundary হলো এমন জায়গা যেখানে internal application behavior external caller-এর সঙ্গে interact করে।
Example HTTP flow:
HTTP Request
↓
API Handler
↓
Application Service
↓
Domain and Repository
Response flow:
Internal result or exception
↓
API Handler
↓
HTTP Response
API handler failure boundary হিসেবে কাজ করতে পারে।
Why Boundaries Matter
Without a deliberate boundary:
- Stack trace client-এর কাছে leak হতে পারে
- Different endpoints different error format দিতে পারে
- Database failure not-found হিসেবে return হতে পারে
- Client retry করা উচিত কি না বুঝতে পারে না
- Same exception multiple layers-এ log হতে পারে
- Sensitive infrastructure details expose হতে পারে
- Unexpected failures inconsistentভাবে handled হতে পারে
Boundary internal complexityকে stable external contract-এ translate করে।
Internal Failure Categories
A practical classification:
Validation failure
Authentication failure
Authorization failure
Not-found failure
Conflict or duplicate failure
Invalid domain state
Rate-limit failure
Infrastructure failure
Unexpected programming failure
Each category may require different response।
Example HTTP Mapping
| Internal condition | Possible HTTP status |
|---|---|
| Invalid request fields | 400 Bad Request |
| Authentication required | 401 Unauthorized |
| Access denied | 403 Forbidden |
| Course not found | 404 Not Found |
| Duplicate course code | 409 Conflict |
| Invalid state transition | 409 Conflict |
| Rate limit exceeded | 429 Too Many Requests |
| Repository unavailable | 500 or 503 |
| Unexpected failure | 500 Internal Server Error |
Exact mapping depends on API conventions।
Consistency is more important than blindly following one table।
A Consistent Error Response
import java.util.List;
public record ErrorResponse(
String code,
String message,
String correlationId,
List<FieldError> fieldErrors
) {
public ErrorResponse {
if (
code == null
|| code.isBlank()
) {
throw new IllegalArgumentException(
"Error code is required."
);
}
if (
message == null
|| message.isBlank()
) {
throw new IllegalArgumentException(
"Error message is required."
);
}
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.",
"correlationId": "req-3a712",
"fieldErrors": [
{
"field": "title",
"code": "COURSE_TITLE_REQUIRED",
"message": "Course title is required."
}
]
}
Stable Code vs Human Message
Error Code
COURSE_NOT_FOUND
DUPLICATE_COURSE_CODE
COURSE_SERVICE_UNAVAILABLE
Used by:
- Frontend logic
- Mobile client
- Tests
- Monitoring
- Localization
Message
The requested course was not found.
Used by humans।
Messages may change or be translated।
Clients should not parse messages programmatically।
Safe User Messages
Internal exception:
CourseRepositoryException:
Query failed against postgresql.internal:5432
Unsafe external response:
Query failed against postgresql.internal:5432
Safe response:
Course information is temporarily unavailable.
Internal logs can preserve technical detail।
Do Not Expose Stack Traces
Never return:
java.lang.NullPointerException
at io.liveklass...
to clients।
Risks:
- Internal class names exposed
- File paths exposed
- Framework details exposed
- Security weaknesses revealed
- Poor user experience
Stack traces belong in secure diagnostic systems।
Correlation ID
A correlation ID uniquely identifies one request or operation across logs and services।
Example:
req-3a712
Client receives:
{
"code": "COURSE_CREATION_FAILED",
"message": "The course could not be created.",
"correlationId": "req-3a712"
}
Support engineer can search logs using:
req-3a712
Why Correlation IDs Help
A user may report:
I received an error while publishing.
Without request identity, finding the exact failure is difficult।
With correlation ID:
Please provide correlation ID req-3a712.
Logs can connect:
API request
Application service
Repository call
External payment call
Final response
Correlation ID Generation
A simple example:
String correlationId =
UUID.randomUUID()
.toString();
In real systems, an existing incoming correlation ID may be accepted and propagated।
Rules:
- Validate incoming format
- Generate one if absent
- Include it in logs
- Include safe value in response
- Propagate to downstream requests
Logging Context
Weak log:
Failed to publish course.
Better:
Failed to publish course.
correlationId=req-3a712
courseCode=JAVA-OOP
instructorId=4001
Useful context may include:
- Correlation ID
- Operation name
- Safe entity identifiers
- Current state
- Exception stack trace
Avoid secrets and unnecessary personal data।
Log Levels
A simplified mental model:
DEBUG
Detailed development or diagnostic information।
INFO
Normal important lifecycle events।
Course published successfully
WARN
Unexpected but potentially recoverable situation।
External service timed out; retry scheduled
ERROR
Operation failed and needs investigation।
Course repository unavailable
Do not log every expected validation failure as an error।
Validation Failure Is Usually Not a Server Error
User submits blank title।
This is expected input failure।
Possible handling:
HTTP 400
No exception stack trace at ERROR level
Structured field response
Logging every bad request with a stack trace creates noise।
Security-related suspicious patterns may still deserve separate monitoring।
Log Once at the Owning Boundary
Weak flow:
Repository logs exception
Service logs same exception
Handler logs same exception
Global handler logs same exception
One failure creates four stack traces।
Better:
- Lower layer adds context through exception wrapping
- Final responsible boundary logs once
- Expected failures may not require stack traces
When Lower Layers Should Log
Lower layer may log when it owns a unique event that outer layers cannot reconstruct।
Examples:
- Retry attempt count
- Circuit breaker opened
- Batch item skipped intentionally
- External provider response metadata
But avoid logging and rethrowing identical failure without new value।
Expected vs Unexpected Failures
Expected
Invalid field
Course not found
Duplicate enrollment
Access denied
Boundary has explicit mapping।
Unexpected
NullPointerException
ArrayIndexOutOfBoundsException
Unknown RuntimeException
Boundary should:
- Log full exception
- Return generic safe response
- Include correlation ID
- Avoid pretending it is validation or not-found
Generic Fallback Handler
Conceptually:
catch (
RuntimeException exception
) {
logger.error(
"Unexpected request failure. correlationId={}",
correlationId,
exception
);
return serverError(
new ErrorResponse(
"INTERNAL_ERROR",
"The request could not be completed.",
correlationId,
List.of()
)
);
}
A broad catch is appropriate at a top-level boundary as a final safety net।
It is usually inappropriate throughout internal business code।
Catch Ordering at a Boundary
try {
return service.publish(
courseCode
);
} catch (
CourseNotFoundException exception
) {
// 404
} catch (
InvalidCourseTransitionException exception
) {
// 409
} catch (
CourseRepositoryException exception
) {
// 503 or 500
} catch (
RuntimeException exception
) {
// Unexpected 500
}
Specific catches first, generic fallback last।
Complete Boundary Example
public final class CourseRequestHandler {
private final CourseService courseService;
private final Logger logger;
public CourseRequestHandler(
CourseService courseService,
Logger logger
) {
if (courseService == null) {
throw new IllegalArgumentException(
"Course service is required."
);
}
if (logger == null) {
throw new IllegalArgumentException(
"Logger is required."
);
}
this.courseService =
courseService;
this.logger =
logger;
}
public ApiResponse<Course> publish(
String rawCourseCode,
String correlationId
) {
try {
CourseCode courseCode =
new CourseCode(
rawCourseCode
);
Course course =
courseService.publish(
courseCode
);
return ApiResponse.success(
200,
course
);
} catch (
IllegalArgumentException exception
) {
return ApiResponse.failure(
400,
new ErrorResponse(
"INVALID_COURSE_CODE",
"Enter a valid course code.",
correlationId,
List.of()
)
);
} catch (
CourseNotFoundException exception
) {
return ApiResponse.failure(
404,
new ErrorResponse(
"COURSE_NOT_FOUND",
"The requested course was not found.",
correlationId,
List.of()
)
);
} catch (
InvalidCourseTransitionException exception
) {
return ApiResponse.failure(
409,
new ErrorResponse(
"COURSE_CANNOT_BE_PUBLISHED",
"The course cannot be published from its current state.",
correlationId,
List.of()
)
);
} catch (
CourseRepositoryException exception
) {
logger.error(
"Course repository failure. correlationId="
+ correlationId,
exception
);
return ApiResponse.failure(
503,
new ErrorResponse(
"COURSE_SERVICE_UNAVAILABLE",
"Course information is temporarily unavailable.",
correlationId,
List.of()
)
);
} catch (
RuntimeException exception
) {
logger.error(
"Unexpected publication failure. correlationId="
+ correlationId,
exception
);
return ApiResponse.failure(
500,
new ErrorResponse(
"INTERNAL_ERROR",
"The request could not be completed.",
correlationId,
List.of()
)
);
}
}
}
ApiResponse এখানে simplified framework-independent model।
Do Not Catch Errors You Cannot Translate Correctly
Weak:
catch (
RuntimeException exception
) {
return notFound();
}
A NullPointerException is not a not-found result।
Generic fallback must map to generic internal failure, not a misleading specific category।
Retryable Failures
Some failures may succeed if attempted again।
Examples:
Temporary network timeout
Service unavailable
Rate limit with retry delay
Transient database connection failure
Possible retryable response:
503 Service Unavailable
or:
429 Too Many Requests
with retry guidance।
Non-Retryable Failures
Examples:
Invalid course code
Access denied
Duplicate enrollment
Unsupported status transition
Retrying the exact same request without changing anything will not help।
Client should fix input, permissions, or state।
Retryability Is Contextual
A database timeout may be retryable।
A database unique constraint violation is not transient।
Both may originate from database operations, but require different treatment।
Do not mark every infrastructure exception retryable।
Retry Only Safe Operations
Before retrying, ask:
Can the operation be repeated without creating duplicate effects?
A read operation is often safer to retry।
A payment charge or enrollment creation may not be safe unless idempotency is designed।
Idempotency
An operation is idempotent when repeating the same request produces the same intended effect without duplicate side effects।
Example:
Set course title to "Java"
Repeating it still results in:
Title = Java
Non-idempotent example:
Add 100 credits
Repeating it adds 200 credits।
Idempotency Key
For a sensitive create operation, client may send:
idempotencyKey=payment-req-7821
Server stores the result associated with that key।
If the same request retries:
- Do not create another charge
- Return the original result
Useful for:
Payments
Orders
Enrollments
External provisioning
Duplicate Enrollment and Idempotency
Suppose enrollment request times out after server successfully saves it।
Client does not know whether it succeeded and retries।
Without idempotency:
Second request may fail as duplicate
With idempotent semantics:
Already enrolled in the same course
may return the existing successful result।
The best response depends on API contract।
Retry Strategy
A retry strategy may include:
- Maximum attempts
- Delay
- Exponential backoff
- Random jitter
- Retryable exception list
- Timeout
- Idempotency guarantee
Avoid immediate infinite retries:
while (true) {
callService();
}
This can worsen an outage।
Exponential Backoff
Concept:
Attempt 1 → Wait 1 second
Attempt 2 → Wait 2 seconds
Attempt 3 → Wait 4 seconds
Jitter adds small randomness so many clients do not retry simultaneously।
Detailed resilience patterns are advanced topics, but retry should always be bounded and deliberate।
Partial Failure
Suppose course publication performs:
- Update course status
- Save database record
- Send email
- Publish analytics event
What if:
Database save succeeds
Email fails
Was the entire operation unsuccessful?
This is a partial failure।
Identify the Primary Transaction
Ask:
What is the core business effect?
For course publication:
Course status persisted as PUBLISHED
Email may be a secondary side effect।
Possible design:
- Commit course publication
- Record notification task
- Retry email separately
- Do not roll back publication only because email failed
Do Not Pretend Atomicity Across Independent Systems
Database transaction cannot usually atomically include:
Database
Email provider
Analytics service
Message broker
Payment provider
If one external system fails, partial completion is possible।
Design must acknowledge this।
Transaction Boundary
A transaction groups operations that should commit or roll back together within supported storage।
Example:
Insert enrollment
Update available seats
Write enrollment audit record
If all are in one database, one transaction may protect consistency।
Either:
All commit
or:
All roll back
Keep Transactions Focused
Avoid holding a database transaction open while:
- Calling a slow external API
- Sending email
- Uploading a large file
- Waiting for user interaction
- Sleeping for retries
Long transactions can:
- Hold locks
- Reduce throughput
- Increase deadlocks
- Increase rollback cost
Transaction Rollback and Exceptions
Many frameworks roll back transactions when an unchecked exception escapes।
But exact behavior depends on framework configuration।
Do not assume every caught exception triggers rollback।
Example:
try {
repository.save(
course
);
} catch (
CourseRepositoryException exception
) {
return false;
}
If the exception is swallowed inside the transaction, framework may see normal completion।
Transaction behavior must be understood explicitly।
Do Not Catch Only to Force Success
Weak:
public boolean createEnrollment() {
try {
saveEnrollment();
updateSeatCount();
return true;
} catch (
RuntimeException exception
) {
return false;
}
}
The caller receives no reason, and transaction semantics may become unclear।
Let the appropriate failure propagate to the transaction boundary unless a valid local recovery exists।
Side Effects After Commit
A safer sequence may be:
1. Validate
2. Start transaction
3. Persist core state
4. Commit transaction
5. Trigger secondary effect
But if step 5 fails, it needs retry or durable tracking।
Simply calling email after commit without recording pending work can lose the notification।
Transactional Outbox: High-Level Idea
A common reliable pattern:
Within one database transaction:
- Save course publication
- Save "CoursePublished" outbox event
A separate worker later:
Reads outbox event
Sends notification
Marks event processed
This reduces the gap between database state and event publishing।
Detailed implementation is an advanced distributed-systems topic।
Batch Processing Failure Strategies
Suppose a job enrolls 1,000 learners।
Possible strategies:
Fail Entire Batch
One failure → Roll back everything
Useful when all records form one atomic unit।
Continue and Collect Failures
Successful items remain
Failed items reported separately
Useful when items are independent।
Stop at First Failure
Simple but may leave partial work।
The strategy must be explicit।
Batch Result Model
public record BatchFailure<T>(
T item,
String code,
String message
) {
}
public record BatchResult<T>(
int successCount,
List<BatchFailure<T>> failures
) {
public BatchResult {
failures =
failures == null
? List.of()
: List.copyOf(
failures
);
}
}
This communicates partial success clearly।
Do Not Return Only false for a Batch
boolean imported
cannot explain:
How many succeeded?
Which items failed?
Can failures be retried?
Was anything committed?
Use structured results when partial completion is possible।
Message Consumer Boundaries
A background message consumer also needs failure policy।
Possible outcomes:
Acknowledge message
Retry later
Move to dead-letter queue
Reject permanently
Example classification:
Invalid message format → Dead-letter
Temporary database failure → Retry
Already processed event → Acknowledge
Unexpected bug → Retry with limit, then dead-letter
Poison Messages
A poison message always fails because its content is permanently invalid।
Infinite retry wastes resources।
Examples:
Missing required event field
Unsupported event version
Invalid identifier
After limited attempts, move it to a dead-letter queue or failure store for investigation।
Scheduled Job Boundaries
A scheduled job should report:
- Start time
- Completion
- Processed count
- Failed count
- Correlation or execution ID
- Final status
- Exception details for unexpected failure
Do not silently stop after the first exception without recording job outcome।
Cancellation and Interruption
When catching:
InterruptedException
restore interruption:
catch (
InterruptedException exception
) {
Thread.currentThread()
.interrupt();
throw new JobInterruptedException(
"Course import was interrupted.",
exception
);
}
A boundary can mark the operation cancelled rather than failed if that distinction matters।
Timeout Is a Failure Contract
External calls should not wait forever।
A reliable boundary should understand:
Connection timeout
Read timeout
Overall operation deadline
Timeout failure may map to:
SERVICE_TIMEOUT
and may be retryable if the operation is safe।
Error Response Should Not Promise What You Do Not Know
Weak message after timeout:
Enrollment failed.
The operation may actually have succeeded remotely before the response was lost।
Safer message:
We could not confirm the enrollment result.
Then use:
- Idempotency key
- Status lookup
- Reconciliation
- Safe retry
This distinction is critical for payments and external systems।
Observability Beyond Logs
Reliable failure boundaries can emit:
- Metrics
- Traces
- Structured logs
- Alerts
Example metrics:
course_publish_success_total
course_publish_failure_total
course_repository_timeout_total
Failures should be measurable, not only printable।
Avoid High-Cardinality Metric Labels
Unsafe metric label:
learnerId
courseId
correlationId
Millions of unique values can overload metrics systems।
Use identifiers in logs or traces।
Metrics labels should use bounded categories:
errorType
operation
status
provider
A Reliable Boundary Checklist
For each operation ask:
- What are expected failures?
- What are unexpected failures?
- Which failures should be logged?
- Which failures are retryable?
- Is retry safe?
- Is the operation idempotent?
- Can partial success occur?
- What is the transaction boundary?
- What error code should the caller receive?
- What internal details must remain hidden?
- How will support find the request?
- What metrics should be recorded?
Complete Example: Enrollment Boundary
Error Codes
public enum EnrollmentErrorCode {
INVALID_REQUEST,
COURSE_NOT_FOUND,
ALREADY_ENROLLED,
ACCESS_DENIED,
SERVICE_UNAVAILABLE,
INTERNAL_ERROR
}
Enrollment Response
public record EnrollmentResponse(
boolean successful,
Enrollment enrollment,
ErrorResponse error
) {
public static EnrollmentResponse success(
Enrollment enrollment
) {
return new EnrollmentResponse(
true,
enrollment,
null
);
}
public static EnrollmentResponse failure(
ErrorResponse error
) {
return new EnrollmentResponse(
false,
null,
error
);
}
}
Boundary Handler
public final class EnrollmentHandler {
private final EnrollmentService service;
private final Logger logger;
public EnrollmentHandler(
EnrollmentService service,
Logger logger
) {
this.service =
service;
this.logger =
logger;
}
public EnrollmentResponse enroll(
long learnerId,
String rawCourseCode,
String correlationId
) {
try {
if (learnerId <= 0) {
throw new IllegalArgumentException(
"Learner ID must be positive."
);
}
CourseCode courseCode =
new CourseCode(
rawCourseCode
);
Enrollment enrollment =
service.enroll(
learnerId,
courseCode
);
return EnrollmentResponse.success(
enrollment
);
} catch (
IllegalArgumentException exception
) {
return failure(
"INVALID_REQUEST",
"The enrollment request is invalid.",
correlationId
);
} catch (
CourseNotFoundException exception
) {
return failure(
"COURSE_NOT_FOUND",
"The requested course was not found.",
correlationId
);
} catch (
DuplicateEnrollmentException exception
) {
return failure(
"ALREADY_ENROLLED",
"The learner is already enrolled in this course.",
correlationId
);
} catch (
AccessDeniedException exception
) {
return failure(
"ACCESS_DENIED",
"You are not allowed to perform this action.",
correlationId
);
} catch (
EnrollmentRepositoryException exception
) {
logger.error(
"Enrollment repository failure. correlationId="
+ correlationId,
exception
);
return failure(
"SERVICE_UNAVAILABLE",
"Enrollment is temporarily unavailable.",
correlationId
);
} catch (
RuntimeException exception
) {
logger.error(
"Unexpected enrollment failure. correlationId="
+ correlationId,
exception
);
return failure(
"INTERNAL_ERROR",
"The enrollment request could not be completed.",
correlationId
);
}
}
private EnrollmentResponse failure(
String code,
String message,
String correlationId
) {
return EnrollmentResponse.failure(
new ErrorResponse(
code,
message,
correlationId,
List.of()
)
);
}
}
Design Review
Why Are Validation Failures Not Logged as Errors?
They are expected caller mistakes, not server defects।
Why Is Repository Failure Logged?
It represents operational failure requiring diagnosis।
Why Is There a Final Runtime Catch?
It prevents raw unexpected exceptions from escaping the external boundary।
Why Is the Message Generic for Unexpected Failure?
The boundary does not know whether internal details are safe।
Why Include Correlation ID?
The response can be connected to internal logs।
Common Mistakes
Returning Raw Exception Messages
May expose internal or sensitive details।
Returning Stack Traces
Leaks implementation information।
Mapping Every Exception to 400
Programming and infrastructure failures are not invalid client input।
Mapping Every Exception to 500
Expected not-found and conflict failures lose meaning।
Logging Every Expected Validation Error at ERROR
Creates operational noise।
Logging the Same Exception at Every Layer
Creates duplicate stack traces।
Retrying Non-Idempotent Operations Blindly
May duplicate charges, enrollments, or notifications।
Retrying Permanent Validation Failures
The same request will continue failing।
Infinite Immediate Retry
Can worsen service outages।
Pretending a Timeout Means Definite Failure
Remote operation may have completed।
Treating Secondary Notification Failure as Core Transaction Failure
May roll back valid business state unnecessarily।
Holding Transactions Open During Slow External Calls
Increases lock time and failure risk।
Returning Only Boolean for Partial Batch Results
Hides committed work and failed items।
Swallowing Exceptions Inside Transactional Methods
May accidentally commit partial state।
Using Correlation IDs as Metric Labels
Creates excessive metric cardinality।
Practice Exercises
Exercise 1: Error Mapping
Map these failures to stable codes and possible HTTP statuses:
- Blank course code
- Course not found
- Duplicate course code
- Access denied
- Database timeout
- Unexpected
NullPointerException
Exercise 2: Safe Messages
Create separate internal and external messages for:
- Database connection failure
- File permission failure
- Payment provider timeout
- Invalid learner ID
Exercise 3: Correlation ID
Design a flow that:
- Reads incoming correlation ID
- Generates one when absent
- Adds it to logs
- Returns it in error responses
- Sends it to downstream services
Exercise 4: Retry Decision
Decide whether each failure is retryable:
- Invalid course code
- Temporary network timeout
- Duplicate enrollment
- Rate limit response
- Access denied
- Temporary database connection failure
Explain whether retry is safe।
Exercise 5: Idempotency
Design an idempotent enrollment endpoint using an idempotency key।
Explain:
- Where the key is stored
- What happens on repeated request
- What response is returned
- How conflicting payloads with the same key are handled
Exercise 6: Partial Failure
Course publication performs:
Save status
Send email
Publish analytics event
Design behavior when:
- Save succeeds
- Email fails
- Analytics succeeds
Define the primary operation and retry strategy।
Exercise 7: Batch Import
Create a batch result that reports:
- Successful course count
- Failed source files
- Error code per failure
- Whether failed items are retryable
Exercise 8: Duplicate Logging
A repository, service, and API handler all log the same exception।
Choose one owning layer for the stack trace and explain what the other layers should do instead।
Predict the Result
Question 1
A request throws CourseNotFoundException and the boundary has a specific catch before RuntimeException.
Which catch executes?
Answer
The CourseNotFoundException catch executes।
Java selects the first compatible catch।
Question 2
A database timeout occurs, but boundary returns:
COURSE_NOT_FOUND
Is this correct?
Answer
No।
A failed lookup operation is different from a successful lookup with no result।
Question 3
A client retries a payment request after timeout without an idempotency key।
What risk exists?
Answer
The payment may be charged more than once if the first request actually succeeded।
Question 4
The database transaction commits course publication, then email sending fails।
Has the course necessarily failed to publish?
Answer
No।
The primary database state may already be committed।
Email failure is a secondary partial failure that may require retry।
Question 5
Should an invalid field response include a full exception stack trace?
Answer
No।
It should return structured validation errors without internal stack details।
Knowledge Check
Question 1
What is a failure boundary?
Question 2
Why use stable error codes?
Question 3
What is the difference between internal and external error messages?
Question 4
What is a correlation ID?
Question 5
Why should expected validation failures not usually be logged as server errors?
Question 6
Where should an unexpected exception usually be logged?
Question 7
What makes a failure retryable?
Question 8
What is idempotency?
Question 9
Why are retries dangerous for non-idempotent operations?
Question 10
What is a partial failure?
Question 11
What is a transaction boundary?
Question 12
Why should transactions avoid slow external calls?
Question 13
Why is a structured batch result better than a boolean?
Question 14
What is a poison message?
Question 15
Why should correlation IDs not be metric labels?
Knowledge Check Answers
Answer 1
A layer that converts internal results and failures into a stable external contract।
Answer 2
Clients can handle failure categories without parsing changeable messages।
Answer 3
Internal messages contain diagnostic detail; external messages must remain safe and user-appropriate।
Answer 4
A request or operation identifier used to connect responses, logs, traces, and downstream calls।
Answer 5
They are expected caller mistakes and logging every one as ERROR creates noise।
Answer 6
At the outer boundary that owns failure reporting and has sufficient request context।
Answer 7
The cause is temporary and repeating the operation has a realistic chance of success।
Answer 8
Repeating the same operation produces the same intended effect without duplicate side effects।
Answer 9
A repeated request may duplicate payments, enrollments, messages, or other effects।
Answer 10
Some parts of a multi-step operation succeed while other parts fail।
Answer 11
The set of storage operations that should commit or roll back together।
Answer 12
They hold locks longer, reduce throughput, and increase timeout and rollback risk।
Answer 13
It communicates success count, failed items, reasons, and retryability।
Answer 14
A permanently invalid message that will fail on every retry।
Answer 15
They are highly unique and can create excessive metrics cardinality।
Lesson Summary
এই lesson-এ আমরা শিখেছি:
- Failure boundary internal failuresকে external contracts-এ translate করে
- HTTP API, jobs, consumers, and CLIs সব failure boundaries হতে পারে
- Validation, authorization, not-found, conflict, infrastructure, and unexpected failures আলাদা categories
- Stable error codes programmatic client handling support করে
- Human messages change বা localize হতে পারে
- Raw stack traces এবং infrastructure details clientsকে expose করা উচিত নয়
- Correlation IDs requests এবং logs connect করে
- Safe entity context logging diagnosis improve করে
- Expected validation failures সাধারণত server errors হিসেবে log করা উচিত নয়
- Unexpected and infrastructure failures owning boundary-তে log করা উচিত
- Same exception every layer-এ log করলে duplicate noise তৈরি হয়
- Specific catches generic fallback-এর আগে থাকে
- Generic outer fallback raw exceptions escaping prevent করে
- Retryable failures temporary; permanent input failures retryable নয়
- Retry safety operation idempotency-এর ওপর depend করে
- Idempotency keys duplicate side effects prevent করতে পারে
- Timeouts outcome uncertain করতে পারে
- Partial failure multi-step operations-এ common
- Primary business effect এবং secondary side effects আলাদা করতে হয়
- Transactions supported storage operationsকে atomically group করে
- Slow external calls transaction-এর মধ্যে রাখা risky
- Side effects after commit durable retry strategy require করতে পারে
- Transactional outbox database state এবং event delivery coordinate করতে সাহায্য করে
- Batch operations explicit all-or-nothing বা partial-success strategy require করে
- Poison messages infinite retry করা উচিত নয়
- Failure metrics bounded categories ব্যবহার করা উচিত
- Reliable boundaries predictable, observable, safe, and consistent failure behavior তৈরি করে
Next Lesson
পরবর্তী lesson:
Module Practice and Assessment
আমরা তৈরি করব:
- Course creation request validation
- Domain invariants
- Custom exceptions
- Repository exception translation
- Consistent error responses
- Correlation IDs
- Checked and unchecked exception decisions
- Retryability classification
- Partial-failure analysis
- Final failure-handling design challenge