- Author: Philippe Collet
We focus here on the implementation of a first component, dedicated to handle customer's carts, enables to show the main mechanisms of the business component layer.
The component is very basic, still we apply interface segregation with two interfaces:
CartModifier: operations to modify a given customer's cart, like adding or removing cookies, and to retrieve the contents of the cart;
public interface CartModifier {
Item update(Long customerId, Item it) throws NegativeQuantityException, CustomerIdNotFoundException;
Set<Item> cartContent(Long customerId) throws CustomerIdNotFoundException;
}CartProcessor: operations for computing the cart's price and validating it to process the associated order;
public interface CartProcessor {
double cartPrice(Long customerId) throws CustomerIdNotFoundException;
Order validate(Long customerId) throws PaymentException, EmptyCartException, CustomerIdNotFoundException;
}Nothing special is needed on these interfaces. They are plain Java. They are simply going to be implemented by the component to be provided by it.
The CartHandler component is a Java class that implements both interfaces while being annotated with @Service.
@Service
public class CartHandler implements CartModifier, CartProcessor {This annotation has the same semantics in Spring as @Component but it is often used to define the service layer of a component-based system. In our case, the service layer of the business part is more or less defined by components that takes or returns Id from the controller layer. Some other components will be completely hidden in the business part, such as Cashier. Some others, like Orderer implements both interfaces that references ids while some other references objects.
The controller part is implemented by RestController components (see the dedicated chapter).
All annotations (@Service, @Component, and @RestController) enable the Spring container to create all found components (by default as singleton) when initializing the web app.
Required interfaces for implementing customers' carts are Payment to process the cart for payment and CustomerFinder. Both interfaces are used in the declaration for two attributes of the component.
The @Autowired annotation is placed on the constructor that initializes both attributes. This annotation will enable the Spring container to inject the reference to the single component implementing this interface when initializing the container. If any @Autowired attribute cannot be injected, the Spring container will raise an exception and stop, before any functional calls through interfaces can be triggered.
private final Payment payment;
private final CustomerFinder customerFinder;
@Autowired
public CartHandler(Payment payment, CustomerFinder customerFinder) {
this.payment = payment;
this.customerFinder = customerFinder;
}Note that for component implementation (not testing), it is preferable to inject at the constructor level rather than at the attribute level, so that all dependencies are well initialized at construction time of the component.
The update method implementation is checking the consistency of the request, e.g., that the customerId references an known custimer, that someone is not removing too much cookies from the cart, and is throwing an exception if needed. At the end, it notably update the customerobject that contains the set of items representing the cart. As the method is annotated with @Transactional, it creates or joins an existing transaction and the customer object will be automatically updated at the end of the method (by the generation of the proper SQL code), or rollback in case of exception.
@Transactional
public Item update(Long customerId, Item item) throws NegativeQuantityException, CustomerIdNotFoundException {
...One interesting implementation is the validate method. After checking for the customer corresponding to the id, it checks that the cart is not empty (or throws EmptyCartException), then delegates the payment to the cashier through the required interface, gets an Order object from it (or a PaymentException), clears the content of the cart and return the Order. Here the Payment interface enables the logic in this component to be restricted to its own responsibility: I check for the cart, someone else handles payment and I get back a created Order I just have to forward back. I don't care about which component is actually serving the Payment.
This is really the essence of component-based software.
@Override
@Transactional
public Order validate(Long customerId) throws PaymentException, EmptyCartException, CustomerIdNotFoundException {
Customer customer = customerFinder.retrieveCustomer(customerId);
if (customer.getCart().isEmpty())
throw new EmptyCartException(customer.getName());
Order newOrder = payment.payOrderFromCart(customer, cartPriceFromCustomer(customer));
customer.clearCart();
return newOrder;
}The component CustomerRegistry is used by CartHandler to find a customer from its id (through the CustomerFinder interface), and is also responsible for creating a customer (through its CustomerRegistration interface).
To implement both interfaces, the component is potentially looking for objects in its data source, or creating a new one in the data source. This is abstracted throug the Repository interface, here extended for the Customer (see the Persistence chapter for details).
@Service
public class CustomerRegistry implements CustomerRegistration, CustomerFinder {
private final CustomerRepository customerRepository;This is illustrated by the usage of findBy and save method from the CustomerRepository interface.
@Override
@Transactional(readOnly = true)
public Optional<Customer> findById(Long id) {
return customerRepository.findById(id);
}
@Override
@Transactional
public Customer register(String name, String creditCard)
throws AlreadyExistingCustomerException {
if (findByName(name).isPresent())
throw new AlreadyExistingCustomerException(name);
Customer newcustomer = new Customer(name, creditCard);
return customerRepository.save(newcustomer);
}