2
\$\begingroup\$

This is order, orderitems and payment linked by foreign key.

Controller:

 @PatchMapping("/orders/buy/{salesJourneyId}")
 @Operation(summary = "To create either card or cash order in cash-hub for BUY flow", description = "To create either card or cash order in cash-hub for BUY flow")
 public ResponseEntity newOrderBuyFlow(
 @RequestHeader("retailerId") String retailerId,
 @RequestHeader("source") String source,
 @RequestHeader("trackingId") String trackingId,
 @PathVariable("salesJourneyId") String salesJourneyId,
 @RequestBody @Valid SalesOrderSubmissionCardRequest orderRequest
 ) {
 validator.validateDealerLoginHeaders();
 ResponseEnvelop<Map<String, Object>> response = salesOrderService.processOrder(salesJourneyId, orderRequest,
 source, trackingId, retailerId);
 return ResponseEntity.ok(response);
 }

Service:

public CashHubResponseEnvelop<Map<String, Object>> processOrder(String salesJourneyId, SalesOrderSubmissionCardRequest orderRequest,
 String source, String trackingId, String retailerId) {
 logger.info("Processing order for Sales Journey ID: {}", salesJourneyId);
 validateRequest(orderRequest);
 Orders order = Orders.builder()
 .orderId(Long.valueOf(orderRequest.getOrderSummary().getId()))
 .orderType(orderRequest.getOrderSummary().getType())
 .description(orderRequest.getOrderSummary().getDescription())
 .amount(BigDecimal.valueOf(orderRequest.getOrderSummary().getTotal()))
 .phone(Long.valueOf(orderRequest.getCustomer().getPhone()))
 .zip(orderRequest.getCustomer().getZip())
 .customerRelationshipId(orderRequest.getCustomer().getCustomerRelationshipId())
 .salesJourneyId(salesJourneyId)
 .build();
 salesJourneyDao.saveOrder(order, source, trackingId, retailerId);
 for (PricePlan pricePlan : orderRequest.getPricePlans()) {
 for (Plan plan : pricePlan.getPlans()) {
 OrderItems orderItem = OrderItems.builder()
 .ordersObjId(order.getOrderId())
 .lob(pricePlan.getLob())
 .planId(plan.getId())
 .unitPrice(BigDecimal.valueOf(plan.getUnitPrice()))
 .quantity(plan.getQuantity())
 .build();
 salesJourneyDao.saveOrderItem(orderItem, source, trackingId);
 }
 }
 String paymentStatus = orderRequest.getBilling().getPaymentMethod().equals("CARD") ? Constants.COMPLETE : Constants.PENDING_POS;
 Payments payment = Payments.builder()
 .orderItemsObjId(order.getOrderId()) // Foreign key to OrderItems
 .cashhubRef(orderRequest.getBilling().getCashhubRef())
 .paymentMethod(orderRequest.getBilling().getPaymentMethod())
 .paymentType(orderRequest.getBilling().getPaymentType())
 .paymentStatus(paymentStatus)
 .build();
 salesJourneyDao.savePayment(payment, source, trackingId);
 Map<String, Object> responseData = new HashMap<>();
 responseData.put("hubConfNumber", orderRequest.getBilling().getCashhubRef());
 logger.info("Response prepared successfully for Order ID: {}", order.getOrderId());
 return ResponseEnvelop.<Map<String, Object>>builder()
 .data(responseData)
 .message("Order created successfully")
 .error(null)
 .build();
 }
private void validateRequest(SalesOrderSubmissionCardRequest orderRequest) {
 if (orderRequest.getCustomer() == null || orderRequest.getPricePlans() == null || orderRequest.getOrderSummary() == null || orderRequest.getBilling() == null) {
 throw new InvalidRequestException("Missing required fields");
 }
 }

Repository


@Repository
public class SalesJourneyDaoImpl implements SalesJourneyDao {
 private final JdbcTemplate jdbcTemplate;
 @Value("${insert.salesJourney}")
 private String insertSalesJourneyQuery;
 @Value("${select.byId}")
 private String selectByIdQuery;
 @Value("${select.bySalesJourneyId}")
 private String selectBySalesJourneyIdQuery;
 @Value("${exists.bySalesJourneyId}")
 private String existsBySalesJourneyIdQuery;
 @Value("${insert.order}")
 private String insertOrderQuery;
 @Value("${insert.orderItem}")
 private String insertOrderItemQuery;
 @Value("${insert.payment}")
 private String insertPaymentQuery;
 @Autowired
 public SalesJourneyDaoImpl(JdbcTemplate jdbcTemplate) {
 this.jdbcTemplate = jdbcTemplate;
 }
 @Override
 public void save(Orders salesJourney) {
 jdbcTemplate.update(
 insertSalesJourneyQuery,
 salesJourney.getSalesJourneyId(),
 salesJourney.getRetailerId(),
 salesJourney.getSource(),
 salesJourney.getTrackingId(),
 Timestamp.valueOf(LocalDateTime.now(ZoneId.of("America/New_York")))
 );
 }
 @Override
 public Optional<Orders> findById(Long id) {
 try {
 Orders salesJourney = jdbcTemplate.queryForObject(
 selectByIdQuery,
 salesJourneyRowMapper(),
 id
 );
 return Optional.ofNullable(salesJourney);
 } catch (EmptyResultDataAccessException e) {
 return Optional.empty();
 }
 }
 @Override
 public Optional<Orders> findBySalesJourneyId(String salesJourneyId) {
 try {
 Orders salesJourney = jdbcTemplate.queryForObject(
 selectBySalesJourneyIdQuery,
 salesJourneyRowMapper(),
 salesJourneyId
 );
 return Optional.ofNullable(salesJourney);
 } catch (EmptyResultDataAccessException e) {
 return Optional.empty();
 }
 }
 @Override
 public boolean existsBySalesJourneyId(String salesJourneyId) {
 Integer count = jdbcTemplate.queryForObject(
 existsBySalesJourneyIdQuery,
 Integer.class,
 salesJourneyId
 );
 return count != null && count > 0;
 }
 private RowMapper<Orders> salesJourneyRowMapper() {
 return new RowMapper<Orders>() {
 @Override
 public Orders mapRow(ResultSet rs, int rowNum) throws SQLException {
 Orders salesJourney = new Orders();
 salesJourney.setId(rs.getLong("id"));
 salesJourney.setSalesJourneyId(rs.getString("sales_journey_id"));
 salesJourney.setRetailerId(rs.getString("retailer_id"));
 salesJourney.setSource(rs.getString("source"));
 salesJourney.setTrackingId(rs.getString("tracking_id"));
 salesJourney.setCreateDate(rs.getTimestamp("created_at").toLocalDateTime());
 return salesJourney;
 }
 };
 }
 public void saveOrder(Orders order, String source, String trackingId, String retailerId) {
 jdbcTemplate.update(insertOrderQuery, order.getOrderId(), order.getOrderType(), order.getDescription(), order.getAmount(),
 order.getPhone(), order.getZip(), order.getCustomerRelationshipId(), order.getSalesJourneyId(), source, trackingId, retailerId,
 Timestamp.valueOf(LocalDateTime.now(ZoneId.of("America/New_York"))));
 }
 public void saveOrderItem(OrderItems orderItem, String source, String trackingId) {
 jdbcTemplate.update(insertOrderItemQuery, orderItem.getOrdersObjId(), orderItem.getLob(), orderItem.getPlanId(), orderItem.getUnitPrice(),
 orderItem.getQuantity(), source, trackingId, Timestamp.valueOf(LocalDateTime.now(ZoneId.of("America/New_York"))));
 }
 public void savePayment(Payments payment, String source, String trackingId) {
 jdbcTemplate.update(insertPaymentQuery, payment.getOrderItemsObjId(), payment.getPaymentMethod(), payment.getPaymentStatus(),
 payment.getCashhubRef(), source, trackingId, Timestamp.valueOf(LocalDateTime.now(ZoneId.of("America/New_York"))));
 }
}

controller advice

@RestControllerAdvice
public class GlobalControllerAdvice extends ResponseEntityExceptionHandler {
 private static final Logger LOGGER = LoggerFactory.getLogger(GlobalControllerAdvice.class);
 @Autowired
 @Qualifier("MessageSource")
 private ResourceBundleMessageSource messageSource;
 @Override
 protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException exception,
 HttpHeaders httpHeaders, HttpStatus httpStatus, WebRequest webRequest) {
 // Map validation errors to the Error class
 List<ErrorCashhub> errors = exception.getBindingResult()
 .getFieldErrors()
 .stream()
 .map(fieldError -> ErrorCashhub.builder()
 .code("ERR_VALIDATION")
 .message(fieldError.getDefaultMessage())
 .details("Field: " + fieldError.getField())
 .build())
 .collect(Collectors.toList());
 return new ResponseEntity<>(errors, httpHeaders, HttpStatus.BAD_REQUEST);
 }
 @ExceptionHandler(CashHubException.class)
 public ResponseEntity<CashHubResponseEnvelop<Void>> handleDealerPortalException(CashHubException ex) {
 return ResponseEntity
 .status(ex.getHttpStatus())
 .body(new CashHubResponseEnvelop<>(ex.getApiError()));
 }
 @ExceptionHandler(MissingRequestHeaderException.class)
 public ResponseEntity<CashHubResponseEnvelop<ErrorCashhub>> handleMissingRequestHeaderException(MissingRequestHeaderException ex) {
 ErrorCashhub error = ErrorCashhub.builder()
 .code(ENUMConfig.ERR_VALIDATION.toString())
 .message(ENUMConfig.ERR_VALIDATION.getValue())
 .details("Required header is missing: " + ex.getHeaderName())
 .build();
 CashHubResponseEnvelop<ErrorCashhub> response = new CashHubResponseEnvelop<>(error);
 return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
 }
 @ExceptionHandler(InvalidHeaderException.class)
 public ResponseEntity<CashHubResponseEnvelop<ErrorCashhub>> handleInvalidHeaderException(InvalidHeaderException ex, HttpServletRequest request) {
 ErrorCashhub error = ErrorCashhub.builder()
 .code(ENUMConfig.ERR_VALIDATION.toString())
 .message(ENUMConfig.ERR_VALIDATION.getValue())
 .details(ex.getMessage())
 .build();
 CashHubResponseEnvelop<ErrorCashhub> response = new CashHubResponseEnvelop<>(error);
 return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
 }
 @ExceptionHandler(Exception.class)
 public ResponseEntity<String> handleException(Exception ex) {
 return new ResponseEntity<>("Internal Server Error: " + ex.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
 }
 @ExceptionHandler(InvalidRequestException.class)
 public ResponseEntity<ErrorCashhub> handleInvalidRequestException(InvalidRequestException ex) {
 ErrorCashhub errorResponse = ErrorCashhub.builder()
 .code(ENUMConfig.ERR_VALIDATION.toString())
 .message(ENUMConfig.ERR_VALIDATION.getValue())
 .details("Invalid request parameters: " + ex.getMessage())
 .build();
 return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
 }
 @ExceptionHandler(DataAccessException.class)
 public ResponseEntity<ErrorCashhub> handleDatabaseException(DataAccessException ex) {
 ErrorCashhub errorResponse = ErrorCashhub.builder()
 .code(ENUMConfig.DATABASE_EXCEPTION_109.toString())
 .message(ENUMConfig.DATABASE_EXCEPTION_109.getValue())
 .details("Error while accessing the database")
 .build();
 return new ResponseEntity<>(errorResponse, HttpStatus.INTERNAL_SERVER_ERROR);
 }
}

Validator:

@Component
public class Validator {
 private static final String SPLIT_REGEX = "\\s*,\\s*";
 private final Logger log = LoggerFactory.getLogger(Validator.class);
 @Autowired
 private WebRequest webRequest;
 @Value("${eligible.sources}")
 private String eligibleSourcesStr;
 @Value("${eligible.dealerLogin.sources}")
 private String eligibleDealerLoginSourcesStr;
 private List<String> eligibleSourceList;
 private List<String> eligibleDealerLoginSourceList;
 Validator() {
 log.info("Validator initialized!");
 }
 @PostConstruct
 private void initFields() {
 if (!empty(eligibleSourcesStr)) {
 eligibleSourceList = Arrays.asList(eligibleSourcesStr.toUpperCase().split(SPLIT_REGEX));
 }
 log.info("eligibleSources: {} ", eligibleSourceList);
 }
 private boolean empty(final String s) {
 return s == null || s.trim().isEmpty();
 }
 public void validateDealerLoginHeaders() {
 final String trackingId = webRequest.getHeader(Constants.HEADER_TRACKING_ID);
 final String source = webRequest.getHeader(Constants.HEADER_SOURCE);
 final String retailerId = webRequest.getHeader(Constants.HEADER_RETAILER_ID);
 if (!isInputValid(trackingId)) {
 log.error(Constants.VALIDATION_ERROR_PRECONDITION_FAILED, ENUMConfig.DEALERPORTAL_SERVICE_COMMON_002);
 throw new InvalidHeaderException("Header '" + trackingId + "' must not be null or empty.");
 } else if (!isInputValid(source)) {
 log.error(Constants.VALIDATION_ERROR_PRECONDITION_FAILED, ENUMConfig.DEALERPORTAL_SERVICE_COMMON_001);
 throw new InvalidHeaderException("Header '" + source + "' must not be null or empty.");
 } else if (!isInputValid(retailerId)) {
 log.error(Constants.VALIDATION_ERROR_PRECONDITION_FAILED, ENUMConfig.DEALERPORTAL_SERVICE_COMMON_004);
 throw new InvalidHeaderException("Header '" + retailerId + "' must not be null or empty.");
 }
 if (!isDealerLoginSourceAuthorized(source)) {
 log.error(Constants.VALIDATION_ERROR_FORBIDDEN, ENUMConfig.DEALERPORTAL_SERVICE_COMMON_003);
 throw new InvalidHeaderException("Header '" + source + "' unauthorized.");
 }
 }
 private boolean isInputValid(String input) {
 return input != null && !input.trim().equals("");
 }
 private boolean isDealerLoginSourceAuthorized(String source) {
 return CollectionUtils.isEmpty(eligibleSourceList) ||
 eligibleSourceList.contains(source.trim().toUpperCase());
 }
}

Some things jumping out to me are logging along with transaction handling. There are one to many and many to one relation between table, can I leverage something else available with jdbctemplate?

Bobby
8,1662 gold badges35 silver badges43 bronze badges
asked Jul 15 at 12:55
\$\endgroup\$
0

1 Answer 1

3
\$\begingroup\$

you already invoke spring validation on SalesOrderSubmissionCardRequest why do you need validateRequest() method? can be replaced with @NotNull(message = "Missing required fields") annotation on fields in the request

processOrder() should be broken down to several methods like saveOrder(), savePricePlans() etc in order to adhere to the single responsibility principle

since you already make use of apache common library, you can implement isInputValid(String input) as StringUtils.isNotBlank(input)

answered Jul 23 at 9:11
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.