ARTICLE AD BOX
First of all, It's my first question here, I'm not experienced enough. But I will grant all the needed information for understadning this.
I'm making an application for a college project, and I'm using spring-boot for the backend and handling all the requests from the frontend app.
One of the functionalities of the app, oversimplifying it, will be managing diverse types payments for the users and generating a log in the data base. All of this via an exclusive controller for all the payment endpoints, that will call a payment service.
While I was making the function that handles one of the payment types at the service (which recieves an specific DTO for that type of payment), I realized that no matter which type of payment I recive, the application will always generate a payment log, so I'm considering encapsulating the functionallity of the specific payment I already made in a method and calling it from a general method that recives a generic payment request DTO (gprDTO for short), checks its contents and type and calls its respective function.
The generic function body would be something like this:
@Transactional public void processGenericPayment(GenericPaymentRequestDTO request) throws AccessDeniedException, IllegalArgumentException{ //0. Check if the user has permission User manager = userRepository.findById(email).orElseThrow(//handle exception); if(!userService.checkAdminAccess(email)) { throw new AccessDeniedException("Not allowed"); } //1. Create the log PaymentLog log = new PaymentLog(); //2. Check the type and call the specific function switch(request.getType()) { case TYPE_X-> { if(request.getUserId() == null || request.getcouponId() == null) throw new IllegalArgumentException("Something"); processPaymentX(request.getUserId(), request.getCouponId()); log.setUser(userRepository.findById(request.getUserId()).orElsethrow(//handle exception) log.setCoupon(couponRepository.findById(request.getCouponId()).orElseThrow(//handle exception)) } case TYPE_Y-> { if(request.getItemId() == null) throw new IllegalArgumentException("Something"); processTypeY(request.getItemId()); log.setItem(itemRepository.findById(request.getItemId()).orElseThrow(//handle exception)) } case TYPE_Z-> { if(request.getUserId() == null) throw new IllegalArgumentException("Something"); processTypeZ(request.getUserId()); log.setItem(itemRepository.findById(request.getUserId()).orElseThrow(//handle exception)) } // All cases } //3. Finish the payment log log.setDate(LocalDateTime.now()); log.setMessage(request.getMessage()); //All fields //4. Save the log logRepository.save(log) }The GenericPaymentRequestDTO (gprDTO mentioned eariler) could be:
@Getter @Setter @NoArgsConstructor @Data public class GenericPaymentRequestDTO { @NotNull private PaymentLogType type; @NotNull private Long managerId; //This is the user that has managed the payment (The service checks if it has the permissions to do this) @NotNull private String message; @NotNull private Double price; private Long userId; //Used in payment type X private Long itemId; //Used in payment type Y private Long couponId; //Used in payment type X & Z //All needed fields... }The PaymentLog entity I have right now is:
@Getter @Setter @Entity @Table(name = "payment_logs") public class PaymentLog implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "log_id", nullable = false) private Long logId; @NotNull @Column(name = "price") private Double price; @NotNull @Column(name = "date", nullable = false) private LocalDateTime date; @NotNull @Column(name = "message") private String message; @NotNull @Column(name="type", nullable = false, columnDefinition = "payment_log_type") private PaymentLogType type; @NotNull @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "manager_id", nullable = false) private User manager; //This fields will be null or not depending of the type. @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "item_id") private InventoryItem item; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "coupon_id") private Coupon coupon; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private User user; //There are more fields, but for simplification, I only added these at the snipet. }I'm not sure if this is a good practice for my app, beacuse not all the payments need the same things. (some need the id of a user, others need an item id, ...) and that could leave many null checks to the gprDTO at the general method. Or maybe, should make one dto for every payment type and avoid all that null checks by adding @NotNull Jakarta annotations at the fields of the specific DTOs.
And another question is if the code which gets the objects of payment log that are not null (item, coupon, user,...) is well written. The specific methods will handle the exception if the entity is not found at the data base, and maybe the orElsethrow() inside the cases of the switch are redundant.
