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:
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.
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:
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);
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); }
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();
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);
}
}
}
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;
ProductController:
@RequestMapping("/update/stock") public String updateStock(Model model) { productService.updateAllStock(); return "redirect:/products"; }
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.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.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.
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
.
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:
List <Products> getAllProducts()
within the ProductService
interfaceProductServiceImpl
ProductRepository
reference within the getAllProducts
method of ProductServiceImpl
to get all the products from ProductRepository
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.