The Service layer

So far so good, we have created a Presentation layer that contains a controller, a dispatcher servlet, view resolvers, and more. And then we created the Domain layer, which contains a single domain class Product. Finally, we created the Persistence layer, which contains a repository interface and an implementation to access our Product domain objects from an in-memory database.

But we are still missing one more concept called the Service layer. Why do we need the Service layer? We have seen a Persistence layer dealing with all data access (CRUD) related logic, a Presentation layer dealing with all web requests and view-related activities, and a Domain layer containing classes to hold information that is retrieved from database records/the Persistence layer. But where can we put the business operations code?

The Service layer exposes business operations that could be composed of multiple CRUD operations. Those CRUD operations are usually performed by the repository objects. For example, you could have a business operation that would process a customer order, and in order to perform such a business operation, you would need to perform the following operations in order:

  1. First, ensure that all the products in the requested order are available in your store.
  2. Second, ensure there are sufficient quantities of those products in your store.
  3. Finally, update the product inventory by reducing the available count for each product ordered.

Service objects are good candidates to put such business operation logic, where it requires multiple CRUD operations to be carried out by the repository layer for a single service call. The service operations could also represent the boundaries of SQL transactions meaning that all the elementary CRUD operations performed inside the business operations should be inside a transaction and either all of them should succeed, or they should roll back in the case of an error.

Time for action - creating a service object

Let's create a service object that will perform the simple business operation of updating stock. Our aim is dead simple: whenever we enter the URL http://localhost:8080/webstore/update/stock/, our web store should go through the inventory of products and add 1,000 units to the existing stock if the number in stock is less than 500:

  1. Open the ProductRepository interface from the com.packt.webstore.domain.repository package in the src/main/java source folder and add one more method declaration in it as follows:
            void updateStock(String productId, long noOfUnits); 
    
  2. Open the InMemoryProductRepository implementation class and add an implementation for the previous declared method as follows:
            @Override 
            public void updateStock(String productId, long noOfUnits) {  
               String SQL = "UPDATE PRODUCTS SET UNITS_IN_STOCK = 
               :unitsInStock WHERE ID = :id";  
               Map<String, Object> params = new HashMap<>(); 
               params.put("unitsInStock", noOfUnits);  
               params.put("id", productId);  
       
               jdbcTemplate.update(SQL, params);  
            } 
    
  3. Create an interface called ProductService under the com.packt.webstore.service package in the src/main/java source folder. And add a method declaration in it as follows:
            void updateAllStock(); 
    
  4. Create a class called ProductServiceImpl under the com.packt.webstore.service.impl package in the src/main/java source folder. And add the following code to it:
            package com.packt.webstore.service.impl; 
     
            import java.util.List; 
     
            import org.springframework.beans.factory.annotation
            .Autowired; 
            import org.springframework.stereotype.Service; 
     
            import com.packt.webstore.domain.Product; 
            import com.packt.webstore.domain.repository
            .ProductRepository; 
            import com.packt.webstore.service.ProductService; 
     
            @Service 
            public class ProductServiceImpl implements ProductService{ 
     
               @Autowired 
               private ProductRepository productRepository; 
       
               @Override 
               public void updateAllStock() { 
                  List<Product> allProducts = 
                  productRepository.getAllProducts(); 
          
                  for(Product product : allProducts) { 
                     if(product.getUnitsInStock()<500) 
                        productRepository.updateStock
                        (product.getProductId(),         
                         product.getUnitsInStock()+1000); 
                  } 
               } 
            } 
    
  5. Open ProductController from the com.packt.webstore.controller package in the src/main/java source folder and add a private reference to ProductService with the @Autowired annotation as follows:
            @Autowired 
            private ProductService productService; 
    
  6. Now add one more method definition as follows in ProductController:
            @RequestMapping("/update/stock") 
            public String updateStock(Model model) { 
               productService.updateAllStock(); 
               return "redirect:/products"; 
            } 
    
  7. Run your application and enter the URL http://localhost:8080/webstore/products. You will be able to see a web page showing all the products. Notice the available units in stock for iPhone 6s will now show as Available 450 units in stock. All other products will show 1,000 as Available 450 units in stock.
  8. Now enter the URL http://localhost:8080/webstore/update/stock, you will be able to see the same web page showing all the products. But this time, you can see that the available units in stock for iPhone 6s have been updated, and will show as Available 1450 units in stock.
    Time for action - creating a service object

    Products page showing the product after stock has been updated via a service call

What just happened?

Okay, before going through the steps I just want to remind you of two facts regarding repository objects—that all the data access (CRUD) operations in a domain object should be carried out through repository objects only. Fact number two is that service objects rely on repository objects to carry out all data access related operations. That's why before creating the actual service interface/implementation, we created a repository interface/implementation method (updateStock) in steps 1 and 2.

The updateStock method from the InMemoryProductRepository class just updates a single product domain object's unitsInStock property for the given product. We need this method when we write logic for our service object method (updateAllStock) in the OrderServiceImpl class.

Now we come to steps 3 and 4 where we created the actual service definition and implementation. In step 3, we created an interface called ProductService to define all the expected responsibilities of an order service. As of now, we defined only one responsibility within that interface, which updates all the stock via the updateAllStock method. In step 4, we implemented the updateAllStock method within the OrderServiceImpl class, where we retrieved all the products and went through them one by one in a for loop to check whether the unitsInStock is less than 500. If so, we add 1,000 more units only to that product.

In the previous exercise, within the ProductController class, we connected to the repository through the ProductRepository interface reference to maximize loose coupling. Similarly, now we have connected the Service layer and repository layer through the ProductRepository interface reference as follows in the ProductServiceImpl class:

@Autowired 
private ProductRepository productRepository; 

As you already learned, Spring assigns the InMemoryProductRepository object to productRepository reference in the previously mentioned code because the productRepository reference has an @Autowired annotation and we know that Spring creates and manages all the @Service and @Repository objects. Remember that OrderServiceImpl has an @Service annotation on top of it.

Tip

To ensure transactional behavior, Spring provides an @Transactional (org.springframework.transaction.annotation.Transactional) annotation. We must annotate service methods with an @Transactional annotation to define transaction attributes, and we need to make some more configurations in our application context to ensure the transactional behavior takes effect.

Since our book is about Spring MVC and the Presentation layer, I omitted the @Transactional annotation from our Service layer objects. To find out more about transactional management in Spring, check out http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html .

Okay, we have created the Service layer, and now it is ready to be consumed from the Presentation layer. It is time for us to connect our Service layer with the controller. In step 5, we created one more controller method called updateStock in ProductController to call the service object method:

@RequestMapping("/update/stock") 
public String updateStock(Model model) { 
   productService.updateAllStock(); 
   return "redirect:/products"; 
} 

The updateStock method from ProductController class uses our productService reference to update all the stock.

You can also see that we mapped the /update/stock URL path to the updateStock method using the @RequestMapping annotation. So finally, when we are trying to hit the URL http://localhost:8080/webstore/update/stock, we are able to see the available units in stock being updated by 1,000 more units for the product P1234.

Have a go hero - accessing the product domain object via a service

In our ProductController class, we only have the ProductRepository reference to access the Product domain object within the list method. But accessing ProductRepository directly from the ProductController is not the best practice, as it is always good to access the Persistence layer repository via a service object.

Why don't you create a Service layer method to mediate between ProductController and ProductRepository? Here are some of things you can try out:

  • Create a method declaration as List <Products> getAllProducts() within the ProductService interface
  • Create an implementation method for the previous method declaration in ProductServiceImpl
  • Use the ProductRepository reference within the getAllProducts method of ProductServiceImpl to get all the products from ProductRepository
  • Remove the ProductRepository reference in the ProductController class and accordingly change the list method in ProductController

After finishing this, you will be able to see the same product listings under the URL http://localhost:8080/webshop/products/ without any problems.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.88.165