Gestionarea robusta a erorilor este esentiala pentru aplicatiile MuleSoft de productie. Acest ghid acopera strategii complete de gestionare a erorilor, de la pattern-uri de baza pana la tehnici avansate de rezilienta.
Concepte fundamentale de gestionare a erorilor
Sa intelegem modelul de erori al MuleSoft:
<!-- Basic try-catch-finally pattern -->
<flow name="basic-error-handling-flow">
<http:listener config-ref="HTTP_Listener_config" path="/orders" method="POST"/>
<try doc:name="Try Block">
<!-- Main processing logic -->
<db:insert config-ref="Database_Config" doc:name="Insert Order">
<db:sql>INSERT INTO orders (customer_id, total) VALUES (:customerId, :total)</db:sql>
<db:input-parameters><![CDATA[#[{
customerId: payload.customerId,
total: payload.total
}]]]></db:input-parameters>
</db:insert>
<http:request
method="POST"
config-ref="Inventory_HTTP_Config"
path="/reserve"
doc:name="Reserve Inventory"/>
<error-handler>
<!-- Handle specific database errors -->
<on-error-propagate type="DB:CONNECTIVITY" doc:name="DB Connection Error">
<logger level="ERROR" message="Database connection failed: #[error.description]"/>
<ee:transform doc:name="DB Error Response">
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{
errorCode: "DB_CONNECTION_ERROR",
message: "Database temporarily unavailable",
retryAfter: 30,
correlationId: correlationId
}]]></ee:set-payload>
</ee:message>
<ee:variables>
<ee:set-variable variableName="httpStatus">503</ee:set-variable>
</ee:variables>
</ee:transform>
</on-error-propagate>
<!-- Handle HTTP request errors -->
<on-error-propagate type="HTTP:CONNECTIVITY, HTTP:TIMEOUT" doc:name="HTTP Error">
<logger level="ERROR" message="External service error: #[error.description]"/>
<ee:transform doc:name="Service Error Response">
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{
errorCode: "EXTERNAL_SERVICE_ERROR",
message: "Inventory service unavailable",
retryAfter: 60,
correlationId: correlationId
}]]></ee:set-payload>
</ee:message>
<ee:variables>
<ee:set-variable variableName="httpStatus">503</ee:set-variable>
</ee:variables>
</ee:transform>
</on-error-propagate>
<!-- Handle validation errors -->
<on-error-propagate type="VALIDATION:*" doc:name="Validation Error">
<ee:transform doc:name="Validation Error Response">
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{
errorCode: "VALIDATION_ERROR",
message: error.description,
details: error.detailedDescription default [],
correlationId: correlationId
}]]></ee:set-payload>
</ee:message>
<ee:variables>
<ee:set-variable variableName="httpStatus">400</ee:set-variable>
</ee:variables>
</ee:transform>
</on-error-propagate>
<!-- Catch-all for unexpected errors -->
<on-error-propagate type="ANY" doc:name="Generic Error">
<logger level="ERROR" message="Unexpected error: #[error.errorType] - #[error.description]"/>
<ee:transform doc:name="Generic Error Response">
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{
errorCode: "INTERNAL_ERROR",
message: "An unexpected error occurred",
correlationId: correlationId
}]]></ee:set-payload>
</ee:message>
<ee:variables>
<ee:set-variable variableName="httpStatus">500</ee:set-variable>
</ee:variables>
</ee:transform>
</on-error-propagate>
</error-handler>
</try>
<!-- Set HTTP status from variable -->
<set-variable variableName="httpStatus" value="#[vars.httpStatus default 200]"/>
</flow>Tipuri de erori personalizate
Defineste si arunca tipuri de erori personalizate:
<!-- Define custom error types in error-types.xml or within flow -->
<flow name="custom-error-types-flow">
<http:listener config-ref="HTTP_Listener_config" path="/customers/{customerId}" method="GET"/>
<!-- Validate customer ID format -->
<choice doc:name="Validate Customer ID">
<when expression="#[attributes.uriParams.customerId matches /^CUST-\d{6}$/]">
<logger level="DEBUG" message="Valid customer ID format"/>
</when>
<otherwise>
<raise-error type="APP:INVALID_CUSTOMER_ID"
description="Customer ID must be in format CUST-XXXXXX"/>
</otherwise>
</choice>
<!-- Query customer -->
<db:select config-ref="Database_Config" doc:name="Get Customer">
<db:sql>SELECT * FROM customers WHERE customer_id = :customerId</db:sql>
<db:input-parameters><![CDATA[#[{
customerId: attributes.uriParams.customerId
}]]]></db:input-parameters>
</db:select>
<!-- Check if customer exists -->
<choice doc:name="Check Customer Exists">
<when expression="#[sizeOf(payload) > 0]">
<ee:transform doc:name="Transform Customer">
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{
customerId: payload[0].customer_id,
name: payload[0].name,
email: payload[0].email,
status: payload[0].status
}]]></ee:set-payload>
</ee:message>
</ee:transform>
</when>
<otherwise>
<raise-error type="APP:CUSTOMER_NOT_FOUND"
description='Customer not found: $(attributes.uriParams.customerId)'/>
</otherwise>
</choice>
<!-- Check customer status -->
<choice doc:name="Check Customer Status">
<when expression="#[payload.status == 'ACTIVE']">
<logger level="DEBUG" message="Customer is active"/>
</when>
<when expression="#[payload.status == 'SUSPENDED']">
<raise-error type="APP:CUSTOMER_SUSPENDED"
description="Customer account is suspended"/>
</when>
<when expression="#[payload.status == 'CLOSED']">
<raise-error type="APP:CUSTOMER_CLOSED"
description="Customer account is closed"/>
</when>
</choice>
<error-handler>
<on-error-propagate type="APP:INVALID_CUSTOMER_ID">
<ee:transform>
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{
error: "INVALID_CUSTOMER_ID",
message: error.description,
httpStatus: 400
}]]></ee:set-payload>
</ee:message>
</ee:transform>
</on-error-propagate>
<on-error-propagate type="APP:CUSTOMER_NOT_FOUND">
<ee:transform>
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{
error: "CUSTOMER_NOT_FOUND",
message: error.description,
httpStatus: 404
}]]></ee:set-payload>
</ee:message>
</ee:transform>
</on-error-propagate>
<on-error-propagate type="APP:CUSTOMER_SUSPENDED, APP:CUSTOMER_CLOSED">
<ee:transform>
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{
error: error.errorType.identifier,
message: error.description,
httpStatus: 403
}]]></ee:set-payload>
</ee:message>
</ee:transform>
</on-error-propagate>
</error-handler>
</flow>Handler global de erori
Centralizeaza gestionarea erorilor in intreaga aplicatie:
<!-- Global error configuration file: global-error-handler.xml -->
<configuration-properties file="error-config.yaml"/>
<global-property name="app.name" value="${app.name}"/>
<global-property name="app.env" value="${app.environment}"/>
<!-- Reusable error handler -->
<error-handler name="Global_Error_Handler">
<!-- Authentication errors -->
<on-error-propagate type="HTTP:UNAUTHORIZED, APIKIT:UNAUTHORIZED">
<flow-ref name="build-auth-error-response"/>
</on-error-propagate>
<!-- Forbidden errors -->
<on-error-propagate type="HTTP:FORBIDDEN">
<flow-ref name="build-forbidden-error-response"/>
</on-error-propagate>
<!-- Not found errors -->
<on-error-propagate type="APIKIT:NOT_FOUND, HTTP:NOT_FOUND">
<flow-ref name="build-not-found-error-response"/>
</on-error-propagate>
<!-- Bad request / Validation errors -->
<on-error-propagate type="APIKIT:BAD_REQUEST, VALIDATION:INVALID_*">
<flow-ref name="build-validation-error-response"/>
</on-error-propagate>
<!-- Method not allowed -->
<on-error-propagate type="APIKIT:METHOD_NOT_ALLOWED">
<flow-ref name="build-method-not-allowed-response"/>
</on-error-propagate>
<!-- Connectivity errors (retryable) -->
<on-error-propagate type="HTTP:CONNECTIVITY, DB:CONNECTIVITY, *:CONNECTIVITY">
<flow-ref name="build-service-unavailable-response"/>
</on-error-propagate>
<!-- Timeout errors -->
<on-error-propagate type="HTTP:TIMEOUT, *:TIMEOUT">
<flow-ref name="build-timeout-error-response"/>
</on-error-propagate>
<!-- Application-specific errors -->
<on-error-propagate type="APP:*">
<flow-ref name="build-app-error-response"/>
</on-error-propagate>
<!-- Catch-all for unexpected errors -->
<on-error-propagate type="ANY">
<flow-ref name="build-internal-error-response"/>
</on-error-propagate>
</error-handler>
<!-- Error response builder sub-flows -->
<sub-flow name="build-auth-error-response">
<ee:transform>
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{
apiVersion: p('api.version'),
error: {
code: "UNAUTHORIZED",
message: "Authentication required",
details: error.description,
timestamp: now() as String {format: "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"},
correlationId: correlationId,
path: attributes.requestPath default "unknown"
}
}]]></ee:set-payload>
</ee:message>
<ee:variables>
<ee:set-variable variableName="httpStatus">401</ee:set-variable>
</ee:variables>
</ee:transform>
<flow-ref name="log-and-track-error"/>
</sub-flow>
<sub-flow name="build-internal-error-response">
<ee:transform>
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{
apiVersion: p('api.version'),
error: {
code: "INTERNAL_SERVER_ERROR",
message: "An unexpected error occurred",
// Don't expose internal error details in production
details: if (p('app.environment') != "prod") error.description else null,
timestamp: now() as String {format: "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"},
correlationId: correlationId,
path: attributes.requestPath default "unknown"
}
}]]></ee:set-payload>
</ee:message>
<ee:variables>
<ee:set-variable variableName="httpStatus">500</ee:set-variable>
</ee:variables>
</ee:transform>
<flow-ref name="log-and-track-error"/>
</sub-flow>
<sub-flow name="log-and-track-error">
<!-- Log error details -->
<logger level="ERROR" doc:name="Log Error">
<ee:message><![CDATA[%dw 2.0
output application/json
---
{
correlationId: correlationId,
errorType: error.errorType.identifier,
errorDescription: error.description,
causeMessage: error.cause.message default null,
stackTrace: if (p('app.environment') != "prod") error.stackTrace else null,
requestPath: attributes.requestPath default "unknown",
httpMethod: attributes.method default "unknown",
timestamp: now()
}]]></ee:message>
</logger>
<!-- Send to error tracking system (e.g., Splunk, DataDog) -->
<async doc:name="Async Error Tracking">
<http:request
method="POST"
config-ref="Error_Tracking_HTTP_Config"
path="/api/errors"
doc:name="Send to Error Tracker">
<http:body><![CDATA[#[{
application: p('app.name'),
environment: p('app.environment'),
correlationId: correlationId,
errorType: error.errorType.identifier,
errorMessage: error.description,
timestamp: now(),
severity: vars.httpStatus match {
case 500 -> "CRITICAL"
case 503 -> "HIGH"
case _ -> "MEDIUM"
}
}]]]></http:body>
</http:request>
</async>
</sub-flow>Strategii de retry
Implementeaza mecanisme inteligente de retry:
<flow name="retry-strategies-flow">
<http:listener config-ref="HTTP_Listener_config" path="/process" method="POST"/>
<!-- Simple retry with until-successful -->
<until-successful
maxRetries="${retry.maxAttempts:3}"
millisBetweenRetries="${retry.intervalMs:2000}"
doc:name="Retry External Call">
<http:request
method="POST"
config-ref="External_Service_Config"
path="/api/process"
doc:name="Call External Service">
<http:response-validator>
<http:success-status-code-validator values="200..299"/>
</http:response-validator>
</http:request>
</until-successful>
<!-- Advanced retry with exponential backoff -->
<set-variable variableName="retryCount" value="#[0]" doc:name="Init Retry Counter"/>
<set-variable variableName="maxRetries" value="#[3]" doc:name="Set Max Retries"/>
<set-variable variableName="baseDelayMs" value="#[1000]" doc:name="Set Base Delay"/>
<until-successful maxRetries="#[vars.maxRetries]" millisBetweenRetries="#[1]">
<try doc:name="Retry Block">
<!-- Calculate exponential backoff delay -->
<choice doc:name="Apply Backoff Delay">
<when expression="#[vars.retryCount > 0]">
<ee:transform doc:name="Calculate Delay">
<ee:variables>
<ee:set-variable variableName="delayMs"><![CDATA[%dw 2.0
output application/java
// Exponential backoff: base * 2^retryCount + random jitter
---
vars.baseDelayMs * (2 pow vars.retryCount) + randomInt(500)]]></ee:set-variable>
</ee:variables>
</ee:transform>
<scripting:execute engine="groovy" doc:name="Sleep">
<scripting:code>Thread.sleep(vars.delayMs as Long)</scripting:code>
</scripting:execute>
</when>
</choice>
<set-variable variableName="retryCount" value="#[vars.retryCount + 1]"/>
<http:request
method="POST"
config-ref="External_Service_Config"
path="/api/unreliable"
doc:name="Call Unreliable Service"/>
<error-handler>
<on-error-continue type="HTTP:CONNECTIVITY, HTTP:TIMEOUT"
when="#[vars.retryCount < vars.maxRetries]">
<logger level="WARN"
message="Retry #[vars.retryCount] of #[vars.maxRetries] - Error: #[error.description]"/>
<raise-error type="MULE:RETRY_EXHAUSTED" description="Triggering retry"/>
</on-error-continue>
<on-error-propagate type="HTTP:CONNECTIVITY, HTTP:TIMEOUT">
<logger level="ERROR" message="All retries exhausted"/>
<raise-error type="APP:SERVICE_UNAVAILABLE"
description="Service unavailable after all retries"/>
</on-error-propagate>
</error-handler>
</try>
</until-successful>
</flow>Pattern-ul circuit breaker
Implementeaza circuit breaker pentru toleranta la erori:
<!-- Circuit breaker configuration using Object Store -->
<os:object-store
name="Circuit_Breaker_Store"
config-ref="ObjectStore_Config"
persistent="true"
entryTtl="300"
entryTtlUnit="SECONDS"/>
<flow name="circuit-breaker-flow">
<http:listener config-ref="HTTP_Listener_config" path="/protected/call" method="POST"/>
<!-- Check circuit state -->
<flow-ref name="check-circuit-state" doc:name="Check Circuit"/>
<choice doc:name="Circuit State Router">
<when expression="#[vars.circuitState == 'OPEN']">
<!-- Circuit is open - fail fast -->
<raise-error type="APP:CIRCUIT_OPEN"
description="Circuit breaker is OPEN. Service temporarily unavailable."/>
</when>
<when expression="#[vars.circuitState == 'HALF_OPEN']">
<!-- Half-open - allow limited traffic -->
<flow-ref name="execute-with-circuit-tracking" doc:name="Half-Open Call"/>
</when>
<otherwise>
<!-- Circuit closed - normal operation -->
<flow-ref name="execute-with-circuit-tracking" doc:name="Normal Call"/>
</otherwise>
</choice>
<error-handler>
<on-error-propagate type="APP:CIRCUIT_OPEN">
<ee:transform>
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{
error: "SERVICE_UNAVAILABLE",
message: "Service is temporarily unavailable. Please retry later.",
retryAfter: vars.circuitResetTime default 60
}]]></ee:set-payload>
</ee:message>
<ee:variables>
<ee:set-variable variableName="httpStatus">503</ee:set-variable>
</ee:variables>
</ee:transform>
</on-error-propagate>
</error-handler>
</flow>
<sub-flow name="check-circuit-state">
<os:retrieve
key="circuit:failure_count"
objectStore="Circuit_Breaker_Store"
target="failureCount"
doc:name="Get Failure Count">
<os:default-value><![CDATA[0]]></os:default-value>
</os:retrieve>
<os:retrieve
key="circuit:last_failure_time"
objectStore="Circuit_Breaker_Store"
target="lastFailureTime"
doc:name="Get Last Failure Time">
<os:default-value><![CDATA[#[0]]]></os:default-value>
</os:retrieve>
<ee:transform doc:name="Determine Circuit State">
<ee:variables>
<ee:set-variable variableName="circuitState"><![CDATA[%dw 2.0
output application/java
var failureThreshold = 5
var resetTimeoutMs = 60000
var timeSinceLastFailure = now() as Number - (vars.lastFailureTime as Number default 0)
---
if (vars.failureCount >= failureThreshold)
if (timeSinceLastFailure > resetTimeoutMs)
"HALF_OPEN"
else
"OPEN"
else
"CLOSED"]]></ee:set-variable>
</ee:variables>
</ee:transform>
<logger level="DEBUG" message="Circuit state: #[vars.circuitState], Failures: #[vars.failureCount]"/>
</sub-flow>
<sub-flow name="execute-with-circuit-tracking">
<try doc:name="Track Circuit Status">
<http:request
method="POST"
config-ref="Protected_Service_Config"
path="/api/endpoint"
doc:name="Call Protected Service">
<http:response-validator>
<http:success-status-code-validator values="200..299"/>
</http:response-validator>
</http:request>
<!-- Success - reset failure count -->
<os:store
key="circuit:failure_count"
objectStore="Circuit_Breaker_Store"
doc:name="Reset Failure Count">
<os:value><![CDATA[0]]></os:value>
</os:store>
<error-handler>
<on-error-propagate type="HTTP:CONNECTIVITY, HTTP:TIMEOUT, HTTP:SERVICE_UNAVAILABLE">
<!-- Increment failure count -->
<os:retrieve
key="circuit:failure_count"
objectStore="Circuit_Breaker_Store"
target="currentFailures"
doc:name="Get Current Failures">
<os:default-value><![CDATA[0]]></os:default-value>
</os:retrieve>
<os:store
key="circuit:failure_count"
objectStore="Circuit_Breaker_Store"
doc:name="Increment Failures">
<os:value><![CDATA[#[vars.currentFailures + 1]]]></os:value>
</os:store>
<os:store
key="circuit:last_failure_time"
objectStore="Circuit_Breaker_Store"
doc:name="Update Last Failure Time">
<os:value><![CDATA[#[now() as Number]]]></os:value>
</os:store>
<logger level="WARN"
message="Circuit failure recorded. Count: #[vars.currentFailures + 1]"/>
</on-error-propagate>
</error-handler>
</try>
</sub-flow>Pattern-ul Dead Letter Queue
Gestioneaza mesajele esuate in mod controlat:
<flow name="message-processing-with-dlq">
<jms:listener config-ref="JMS_Config" destination="orders.queue" doc:name="Listen for Orders"/>
<set-variable variableName="originalPayload" value="#[payload]" doc:name="Store Original"/>
<set-variable variableName="messageId" value="#[attributes.messageId]" doc:name="Store Message ID"/>
<try doc:name="Process Message">
<!-- Validate message -->
<flow-ref name="validate-order-message" doc:name="Validate"/>
<!-- Process order -->
<flow-ref name="process-order" doc:name="Process"/>
<error-handler>
<!-- Transient errors - retry -->
<on-error-continue type="HTTP:CONNECTIVITY, DB:CONNECTIVITY"
when="#[vars.retryCount default 0 < 3]">
<set-variable variableName="retryCount" value="#[(vars.retryCount default 0) + 1]"/>
<logger level="WARN" message="Transient error, scheduling retry #[vars.retryCount]"/>
<!-- Requeue with delay -->
<jms:publish config-ref="JMS_Config" destination="orders.retry.queue" doc:name="Requeue">
<jms:message>
<jms:body><![CDATA[#[vars.originalPayload]]]></jms:body>
<jms:properties><![CDATA[#[{
retryCount: vars.retryCount,
originalMessageId: vars.messageId,
lastError: error.description
}]]]></jms:properties>
</jms:message>
</jms:publish>
</on-error-continue>
<!-- Permanent failures - send to DLQ -->
<on-error-continue type="ANY">
<logger level="ERROR" message="Permanent failure, sending to DLQ: #[error.description]"/>
<ee:transform doc:name="Build DLQ Message">
<ee:message>
<ee:set-payload><![CDATA[%dw 2.0
output application/json
---
{
originalMessage: vars.originalPayload,
error: {
type: error.errorType.identifier,
description: error.description,
timestamp: now()
},
metadata: {
messageId: vars.messageId,
retryCount: vars.retryCount default 0,
source: "orders.queue",
processedAt: now()
}
}]]></ee:set-payload>
</ee:message>
</ee:transform>
<jms:publish config-ref="JMS_Config" destination="orders.dlq" doc:name="Send to DLQ"/>
<!-- Alert on DLQ -->
<async doc:name="Alert Team">
<http:request
method="POST"
config-ref="Slack_Webhook_Config"
path="/services/webhook"
doc:name="Slack Alert">
<http:body><![CDATA[#[{
text: "⚠️ Message sent to DLQ",
attachments: [{
color: "danger",
fields: [
{title: "Message ID", value: vars.messageId, short: true},
{title: "Error", value: error.description, short: false}
]
}]
}]]]></http:body>
</http:request>
</async>
</on-error-continue>
</error-handler>
</try>
</flow>Concluzie
Gestionarea eficienta a erorilor in MuleSoft necesita o abordare pe mai multe niveluri: handlere specifice pentru erorile cunoscute, handlere globale pentru consistenta, strategii de retry pentru erorile tranzitorii si circuit breaker pentru protectia sistemului. Implementeaza logging si alerte comprehensive pentru a mentine vizibilitatea. Aceste pattern-uri asigura ca integrarile tale raman reziliente si usor de intretinut in productie.
Sistemul tau AI e conform cu EU AI Act? Evaluare gratuita de risc - afla in 2 minute →