Skip to content

Commit

Permalink
🛡️implement exception handling across microservices
Browse files Browse the repository at this point in the history
  • Loading branch information
Adnane Miliari committed Nov 29, 2024
1 parent 8506e98 commit dc3e201
Show file tree
Hide file tree
Showing 47 changed files with 782 additions and 323 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package exceptionhandler.business;

import exceptionhandler.core.BaseException;

public class CustomerException extends BaseException {
public CustomerException(String message) {
super(message);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package exceptionhandler.business;

import exceptionhandler.core.BaseException;

public class NotificationException extends BaseException {
public NotificationException(String message) {
super(message);
}
}
10 changes: 10 additions & 0 deletions common/src/main/java/exceptionhandler/business/OrderException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package exceptionhandler.business;

import exceptionhandler.core.BaseException;

public class OrderException extends BaseException {
public OrderException(String message) {
super(message);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package exceptionhandler.business;

import exceptionhandler.core.BaseException;

public class PaymentException extends BaseException {
public PaymentException(String message) {
super(message);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package exceptionhandler.business;

import exceptionhandler.core.BaseException;

public class ProductException extends BaseException {
public ProductException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package exceptionhandler.core;

public class BadRequestException extends BaseException {
public BadRequestException(String message) {
super(message);
}
}
7 changes: 7 additions & 0 deletions common/src/main/java/exceptionhandler/core/BaseException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package exceptionhandler.core;

public abstract class BaseException extends RuntimeException {
public BaseException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package exceptionhandler.core;

public class DuplicateResourceException extends BaseException {
public DuplicateResourceException(String message) {
super(message);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package exceptionhandler.core;

public class ResourceNotFoundException extends BaseException {
public ResourceNotFoundException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package exceptionhandler.core;

import exceptionhandler.payload.ValidationError;
import lombok.Getter;

import java.util.List;

@Getter
public class ValidationException extends BaseException {
private final List<ValidationError> errors;

public ValidationException(String message, List<ValidationError> errors) {
super(message);
this.errors = errors;
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package exceptionhandler.handler;

import exceptionhandler.business.CustomerException;
import exceptionhandler.business.NotificationException;
import exceptionhandler.business.OrderException;
import exceptionhandler.business.PaymentException;
import exceptionhandler.business.ProductException;
import exceptionhandler.core.BaseException;
import exceptionhandler.core.DuplicateResourceException;
import exceptionhandler.core.ResourceNotFoundException;
import exceptionhandler.core.ValidationException;
import exceptionhandler.payload.ErrorCode;
import exceptionhandler.payload.ErrorDetails;
import exceptionhandler.payload.ValidationError;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import java.time.LocalDateTime;
import java.util.List;

@RestControllerAdvice
@Slf4j
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {

@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ResponseEntity<ErrorDetails> handleResourceNotFoundException(
ResourceNotFoundException ex, WebRequest request) {
return buildErrorResponse(ex.getMessage(),
ErrorCode.RESOURCE_NOT_FOUND,
HttpStatus.NOT_FOUND,
request);
}

@ExceptionHandler(ValidationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<ErrorDetails> handleValidationException(
ValidationException ex, WebRequest request) {
return buildErrorResponse(ex.getMessage(),
ErrorCode.VALIDATION_FAILED,
HttpStatus.BAD_REQUEST,
request,
ex.getErrors());
}

@ExceptionHandler(DuplicateResourceException.class)
@ResponseStatus(HttpStatus.CONFLICT)
public ResponseEntity<ErrorDetails> handleDuplicateResourceException(
DuplicateResourceException ex, WebRequest request) {
return buildErrorResponse(ex.getMessage(),
ErrorCode.DUPLICATE_RESOURCE,
HttpStatus.CONFLICT,
request);
}

@ExceptionHandler({
CustomerException.class,
OrderException.class,
PaymentException.class,
ProductException.class,
NotificationException.class
})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ResponseEntity<ErrorDetails> handleBusinessExceptions(
BaseException ex, WebRequest request) {
return buildErrorResponse(ex.getMessage(),
ErrorCode.BAD_REQUEST,
HttpStatus.BAD_REQUEST,
request);
}

@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ResponseEntity<ErrorDetails> handleAllUncaughtException(
Exception ex, WebRequest request) {
return buildErrorResponse(ex.getMessage(),
ErrorCode.INTERNAL_ERROR,
HttpStatus.INTERNAL_SERVER_ERROR,
request);
}

private ResponseEntity<ErrorDetails> buildErrorResponse(
String message,
ErrorCode errorCode,
HttpStatus status,
WebRequest request) {
return buildErrorResponse(message, errorCode, status, request, null);
}

private ResponseEntity<ErrorDetails> buildErrorResponse(
String message,
ErrorCode errorCode,
HttpStatus status,
WebRequest request,
List<ValidationError> errors) {

ErrorDetails errorDetails = ErrorDetails.builder()
.timestamp(LocalDateTime.now())
.code(errorCode.getCode())
.message(message)
.path(((ServletWebRequest) request).getRequest().getRequestURI())
.errors(errors)
.build();

return new ResponseEntity<>(errorDetails, status);
}
}

20 changes: 20 additions & 0 deletions common/src/main/java/exceptionhandler/payload/ErrorCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package exceptionhandler.payload;

public enum ErrorCode {
RESOURCE_NOT_FOUND("ERR_001"),
VALIDATION_FAILED("ERR_002"),
DUPLICATE_RESOURCE("ERR_003"),
BAD_REQUEST("ERR_004"),
INTERNAL_ERROR("ERR_005");

private final String code;

ErrorCode(String code) {
this.code = code;
}

public String getCode() {
return code;
}
}

18 changes: 18 additions & 0 deletions common/src/main/java/exceptionhandler/payload/ErrorDetails.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package exceptionhandler.payload;

import lombok.Builder;
import lombok.Getter;

import java.time.LocalDateTime;
import java.util.List;

@Getter
@Builder
public class ErrorDetails {
private LocalDateTime timestamp;
private String code;
private String message;
private String path;
private List<ValidationError> errors;
}

12 changes: 12 additions & 0 deletions common/src/main/java/exceptionhandler/payload/ValidationError.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package exceptionhandler.payload;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class ValidationError {
private String field;
private String message;
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@

public class CustomerConstant {
public static final String CUSTOMER_URI_REST_API = "/api/v1/customers";
public static final String CUSTOMER_NOT_FOUND_EXCEPTION = "User not found";
public static final String CUSTOMER_NOT_FOUND = "Customer with ID %d not found";
public static final String CUSTOMER_EMAIL_EXISTS = "Customer with email %s already exists";
public static final String NO_CUSTOMERS_FOUND = "No customers found";
}

75 changes: 39 additions & 36 deletions customer/src/main/java/dev/nano/customer/CustomerController.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@
import dev.nano.clients.order.OrderResponse;
import dev.nano.clients.payment.PaymentRequest;
import dev.nano.clients.payment.PaymentResponse;
import jakarta.validation.Valid;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

import static dev.nano.customer.CustomerConstant.CUSTOMER_URI_REST_API;


Expand All @@ -21,53 +23,54 @@ public class CustomerController {

private final CustomerService customerService;

@GetMapping(
path = "/{customerId}",
produces={MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}
)
public ResponseEntity<CustomerDTO> getCustomer(@PathVariable("customerId") Long customerId)
throws CustomerNotFoundException {
@GetMapping(path = "/{customerId}")
public ResponseEntity<CustomerDTO> getCustomer(@PathVariable("customerId") Long customerId) {
log.info("Retrieving customer with ID: {}", customerId);
return ResponseEntity.ok(customerService.getCustomer(customerId));
}

log.info("Retrieving customer with id {}", customerId);
return new ResponseEntity<>(
customerService.getCustomer(customerId),
HttpStatus.OK
);
@GetMapping
public ResponseEntity<List<CustomerDTO>> getAllCustomers() {
log.info("Retrieving all customers");
return ResponseEntity.ok(customerService.getAllCustomers());
}

@PostMapping(
path = "/add",
consumes={MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE},
produces={MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}
)
public ResponseEntity<CustomerDTO> createNewCustomer(@RequestBody CustomerDTO customerDTO) {
log.info("Add new customer {}", customerDTO);
@PostMapping("/add")
public ResponseEntity<CustomerDTO> createCustomer(@Valid @RequestBody CustomerDTO customerDTO) {
log.info("Creating new customer: {}", customerDTO);
return new ResponseEntity<>(
customerService.createCustomer(customerDTO),
HttpStatus.CREATED
customerService.createCustomer(customerDTO),
HttpStatus.CREATED
);
}

@PostMapping(
path = "/orders",
produces={MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}
)
public ResponseEntity<OrderResponse> customerOrders(@RequestBody OrderRequest orderRequest) {
@PutMapping("/{customerId}")
public ResponseEntity<CustomerDTO> updateCustomer(
@PathVariable Long customerId,
@Valid @RequestBody CustomerDTO customerDTO) {
log.info("Updating customer with ID {}: {}", customerId, customerDTO);
return ResponseEntity.ok(customerService.updateCustomer(customerId, customerDTO));
}

@DeleteMapping("/{customerId}")
public ResponseEntity<Void> deleteCustomer(@PathVariable Long customerId) {
log.info("Deleting customer with ID: {}", customerId);
customerService.deleteCustomer(customerId);
return ResponseEntity.noContent().build();
}

log.info("Customer orders {}", orderRequest);
@PostMapping("/orders")
public ResponseEntity<OrderResponse> customerOrders(@Valid @RequestBody OrderRequest orderRequest) {
log.info("Processing order for customer: {}", orderRequest);
return new ResponseEntity<>(
customerService.customerOrders(orderRequest),
HttpStatus.CREATED
customerService.customerOrders(orderRequest),
HttpStatus.CREATED
);
}

@PostMapping(
path = "/payment",
produces={MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE}
)
public ResponseEntity<PaymentResponse> customerPayment(@RequestBody PaymentRequest paymentRequest) {

log.info("Customer payment {}", paymentRequest);
@PostMapping("/payment")
public ResponseEntity<PaymentResponse> customerPayment(@Valid @RequestBody PaymentRequest paymentRequest) {
log.info("Processing payment for customer: {}", paymentRequest);
return new ResponseEntity<>(
customerService.customerPayment(paymentRequest),
HttpStatus.CREATED
Expand Down
Loading

0 comments on commit dc3e201

Please sign in to comment.