javaoopdesign-patternsapi-design

E-Commerce low level design of Order class


I am in the middle of shopping website design. As of now, I am stuck at one point and need a little feedback. Let us say I have an order class defined as follow:

class Order {

    Payment payment;
    Address ShippingAddress
    int orderId;
    List<Item> orderItem;
    double orderValue;  
    Buyer buyer;
    Date orderDate;
    NotificationService notificationService;
    Shipment shipment;
    
    List<OrderLog> orderLog;

    public OrderStatus placeOrder();
    public OrderStatus trackOrder();
    public void addOrderLogs();
    public PaymentInfo makePayment();
    public int createShipment();

}

Does it make sense to have placeOrder(), makePayment() etc.,API in order class? Or shall I make a separate OrderManager that helps to do all order related stuff and order will act as a pojo?

To me first one seems correct because the placeOrder(), makePayment() seems to me as behaviours of order and they should be in order class but on other class I think it is too much for an order class to do. Am I violating some SOLID principle by taking one approach over another? Since the OrderManager will also do same thing that we add in this class, do does it make sense to move it out? placeOrder(), makePayment() etc, seems behavious of Order to me. Any thoughts?

On the extended note, how to be confident in deciding what should reside in a class and what shouldn't?


Solution

  • In my opinion, I think:

    1. If Order class is a data class, and you will use this class to store to database, I think you should move the functions:
    public OrderStatus placeOrder();
    public OrderStatus trackOrder();
    public void addOrderLogs();
    public PaymentInfo makePayment();
    public int createShipment();
    

    from Order class to OrderManager class, because to implement every function (i.e placeOrder) maybe you need:

    private OrderValidator orderValidator;
    private OrderService orderService;
    
    public OrderStatus placeOrder() {
        if(orderValidator.validateOrder(this)) {
            orderService.saveOrder(this);
            return OrderStatus.OK;
        }
        return OrderStatus.NOK
    }
    

    but when you use a JPA (Entity) framework like Hibernate, or Spring JPA, they will load data from database and have no information of orderValidator or orderService to set to Order object. And you will need annotate orderValidtor and orderService with @Transient annotation, it makes you source code is complicated

    2. If Order class is a composite class, maybe you can keep the functions in Order class and you don't need OrderManager class anymore, but you should combine your pattern with Factory pattern and Builder pattern like this:
    interface IOrder {
        Payment getPayment();
        Address getShippingAddress();
        //....
    }
    
    interface OrderValidator {
        boolean validateOrder(IOrder order);
    }
    
    interface OrderService {
        void saveOrder(IOrder order);
    }
    
    class OrderBuilder {
        private OrderValidator orderValidator;
        private OrderService orderService;
        private Payment payment;
        // other fields
    
        public OrderBuilder orderValidator(OrderValidator orderValidator) {
            this.orderValidator = orderValidator;
            return this;
        }
    
        public OrderBuilder orderService(OrderService orderService) {
            this.orderService = orderService;
            return this;
        }
        public OrderBuilder payment(Payment payment) {
            this.payment = payment;
            return this;
        }
    
        public Order build() {
            Order order = new Order();
            order.setOrderService(orderService);
            order.setOrderValidator(orderValidator);
            order.setPayment(payment);
            //set others
            return order;
        }
    }
    
    // Singleton
    class OrderFactory {
        private OrderValidator orderValidator;
        private OrderService orderService;
        
        public OrderFactory(OrderValidator orderValidator, OrderService orderService) {
            this.orderValidator = orderValidator;
            this.orderService = orderService;
        }
    
        public OrderBuilder newOrderBuilder() {
            return new OrderBuilder()
                    .orderValidator(orderValidator)
                    .orderService(orderService);
        }
    }
    
    @Setter
    @Getter
    class Order implements IOrder
    
    // usage
    OrderFactory orderFactory = new OrderFactory(orderValidator, orderService);
    Order order1 = orderFactory.newOrderBuilder()
            .payment(payment)
            // set others
            .build();
    order1.placeOrder();
    Order order2 = orderFactory.newOrderBuilder()
           .payment(payment)
           // set others
           .build();
    order2.placeOrder();