Deep into the Service Layer

Introduction

In the past tutorials, we have performed some CRUD operations on entities and learned how to manage different types of relationships between them. Now, it is time to focus on the business operations. When selling our lemonades, we need to update the stock of products based on their recipe. In this tutorial, we will explore how to use the service layer to create an order with all the business logic implied.

Lemonade application

What do we create?

The main goal of this tutorial is to create the selling functionality. Let’s revisit the problem and try to create a diagram for our last entity.

Now that we can handle our stocks and recipes, we need to start selling. When a customer comes, we should be able to create an order to sell the lemonades. For each order, we need to ask the customer what lemonade he wants to buy and the quantity and we need to calculate the total price. When a lemonade is sold, the product stock should be updated according to the recipe.

As we can see, we need to create a new entity: Order. It will contain a lemonade, quantity, and the final price that the customer needs to pay. We need to update our diagram with this new entity. Below we can see the final version of our application diagram.

lemonade stand class diagram

We already know how to handle the entire flow in our multi-layer application for the new entity, but a little bit of practice is always welcomed. The challenge this time is the operations required when we make a sale. We need to check the recipe of the lemonade to see if we have enough stock of the products. If the stock is sufficient, we need to update it and finalize the order by calculating the final price.

How do we create it ?

As we have seen before, the first step is creating the entity. We can refer to the diagram and create a class with the required attributes: a lemonade, quantity, final price.

				
					public class Order extends Entity {

    private Lemonade lemonade;

    private int quantity;

    private int finalPrice;
    
    private Date date;

    public Order(int id, Lemonade lemonade, int quantity, int finalPrice, Date date) {
        super.id = id;
        this.lemonade = lemonade;
        this.quantity = quantity;
        this.finalPrice = finalPrice;
        this.date = date;
    }
    
    //setters and getters

}
				
			

As we have seen before, the first step is creating the entity. We can refer to the diagram and create a class with the required attributes: a lemonade, quantity, final price.

				
					1,1,1,12,2024-06-10
				
			

The service layer contains all the business logic. This is the place where we should validate and update the stock, and then calculate the final price. We need to use other services for these kind of operations, so when creating the class, we should pass the services into the constructor.

				
					public class OrderService {

    private RepositoryInterface<Order> orderRepository;

    private LemonadeService lemonadeService;

    private ProductService productService;

    public OrderService(RepositoryInterface orderRepository, LemonadeService lemonadeService, ProductService productService) {
        this.orderRepository = orderRepository;
        this.lemonadeService = lemonadeService;
        this.productService = productService;
    }
    
}
				
			

Let’s create the save method, which contains all the logic:

				
					public Order saveOrder(int id, int lemonadeId, int orderQuantity) throws ValidationException, IDNotUniqueException {
        Lemonade lemonade = lemonadeService.findById(lemonadeId);
        List<LemonadeRecipe> recipe = lemonadeService.findLemonadeRecipe(lemonadeId);

        boolean isEnoughStock = isEnoughStockForOrder(recipe, orderQuantity);
        if (isEnoughStock) {
            updateProductStock(recipe, orderQuantity);
            Order order = new Order(id, lemonade, orderQuantity, orderQuantity * lemonade.getTotalPrice(), new Date());
            Order savedOrder = orderRepository.save(order);

            return savedOrder;
        } else {
            throw new ValidationException("Not enough stock for the order");
        }
    }

    private void updateProductStock(List<LemonadeRecipe> recipe, int orderQuantity) throws ValidationException {
        for (LemonadeRecipe lemonadeRecipe : recipe) {
            Product product = productService.findById(lemonadeRecipe.getProduct().getId());
            int totalQuantity = lemonadeRecipe.getQuantity() * orderQuantity;

            if (totalQuantity <= product.getQuantity()) {
                product.setQuantity(product.getQuantity() - totalQuantity);
                productService.updateProduct(product.getId(), product.getName(), product.getDescription(), product.getPrice(), product.getQuantity(), product.getSupplier().getId());
            }
        }
    }

    private boolean isEnoughStockForOrder(List<LemonadeRecipe> recipe, int orderQuantity) {
        boolean isValid = true;
        for (LemonadeRecipe lemonadeRecipe : recipe) {
            Product product = productService.findById(lemonadeRecipe.getProduct().getId());
            int totalQuantity = lemonadeRecipe.getQuantity() * orderQuantity;

            if (product.getQuantity() < totalQuantity) {
                isValid = false;
            }
        }

        return isValid;
    }

				
			

The next layer that should be updated is the UI Layer, represented in our case by the UserInterface class. We need to add an option in the menu and call the newly added save method.

Our main is updated as well, because we need to create OrderFileRepository and OrderService and pass them to the Console.

				
					    public void runOrderOption(Scanner scanner) {
        System.out.println("You want to create a new order.");

        System.out.print("Order id: ");
        int id = scanner.nextInt();

        System.out.print("Lemonade id: ");
        int lemonadeId = scanner.nextInt();

        System.out.print("Quantity: ");
        int quantity = scanner.nextInt();

        try {
            Order order = orderService.saveOrder(id, lemonadeId, quantity);
            System.out.printf("The order with ID=%s has been saved \n", order.getId());
        } catch (ValidationException | IDNotUniqueException e) {
            System.out.println("Error with saving the order: " + e.getMessage());
        }
    }
				
			
				
					public static void main(String[] args) throws ValidationException, IDNotUniqueException {
        
        SupplierFileRepository supplierRepository = new SupplierFileRepository("suppliers.csv");
        SupplierValidator supplierValidator = new SupplierValidator();
        SupplierService supplierService = new SupplierService(supplierRepository, supplierValidator);

        ProductFileRepository productFileRepository = new ProductFileRepository("products.csv");
        ProductValidator productValidator = new ProductValidator();
        ProductService productService = new ProductService(productFileRepository, productValidator, supplierService);

        LemonadeFileRepository lemonadeFileRepository = new LemonadeFileRepository("lemonades.csv");
        LemonadeRecipeFileRepository lemonadeRecipeFileRepository = new LemonadeRecipeFileRepository("lemonadesrecipes.csv");
        LemonadeService lemonadeService = new LemonadeService(lemonadeRecipeFileRepository, lemonadeFileRepository, productService);

        OrderFileRepository orderFileRepository = new OrderFileRepository("orders.csv");
        OrderService orderService = new OrderService(orderFileRepository, lemonadeService, productService);

        UserInterface userInterface = new UserInterface(productService, supplierService, lemonadeService, orderService);

        MainTest mainTest = new MainTest();
        mainTest.runAllTest();
        userInterface.runMenu();
    }
				
			

Key points

  • sometimes we need to perform some additional operations when working with entities.
  • the service layer is responsible for containing the business logic of an application, acting as an intermediary between the presentation layer and the data access layer, ensuring that data is processed correctly and consistently

Conclusion

In this tutorial, we have discovered how to handle business logic when more entities are involved. We used different services to create a new order. Next time, we will handle the final part of our application: generating reports to see how much money we have made and what stock needs to be updated.