Building a Service Layer

Building a Service Layer

Spring Application Deployed with Kubernetes

Step by step building an application using Spring Boot and deployed via Docker on Kubernetes with Helm

full course
  1. Setup: IDE and New Project
  2. Create the Data Repository
  3. Building a Service Layer
  4. Create a REST Controller
  5. Logging, Tracing and Error Handling
  6. Documentation and Code Coverage
  7. Database as a Service
  8. Containerize the Service With Docker
  9. Docker Registry
  10. Automated Build Pipeline
  11. Helm for Deployment
  12. Setting up a Kubernetes Cluster
  13. Automating Deployment (for CICD)
  14. System Design
  15. Messaging and Event Driven Design
  16. Web UI with React
  17. Containerizing our UI
  18. UI Build Pipeline
  19. Put the UI in to Helm
  20. Creating an Ingress in Kubernetes
  21. Simplify Deployment
  22. Conclusion and Review

In this stage we’re going to build out the service or logical layer of our microservice. I’ll explain adapter and port or hexagonal architecture and covering the usage of mapstruct.

Service Design

Ports and adapter or hexagonal design is a well discussed concept, so I’m not going to go into the pro and con of the design. Amazingly, the concepts work at the class, service or enterprise application scale. Additionally, spring’s use of interfaces makes implementing hexagonal architecture extremely easy. I’ll explain quickly the design of our service before we go much further and show how hexagonal design will be applied.

Ideally, we would like our service layer to only ‘talk’ in the concept of a model object. As we push the REST and DB concepts to the edges we will maintain a different structure, even though they may look exactly the same. The reason for this is that our java beans tend to get loaded up with annotations that are specific to the port or adapter that they function with.

For example, our CustomerEntity object is currently loaded up with spring data annotations used for mapping to the database fields. These are only relevant for the DB interaction (completely useless at the rest endpoint). Additionally, loading up our model with annotations for every port or adapter makes the model object difficult to read, maintain and evolve.

However, this does come at a cost, in that we need to map our model object to the structures used by the ports and adapters. Mapping or translating is not to be taken lightly as this becomes a very risky portion of our application in terms of its evolution (but actually helps with service governance, which we will cover later). Additionally, writing bean mappers is tedious. So we’re introducing tedious and error prone code, how do we mitigate that? We can use mapstruct framework to do the heavy lifting.

Introducing Mapstruct

Mapstruct is a framework that is designed for mapping data between beans. It is very useful for handing simple cases, but it is also powerful enough to handle complex or custom mapping. Take a look at the documentation and examples.

Using Mapstruct

Create the Model

Lets start by creating our model object in com.brianrook.medium.customer.service.model. Create a java class called Customer and add this content:

package com.brianrook.medium.customer.service.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Customer {
    private Long customerId;
    private String firstName;
    private String lastName;
    private String phoneNumber;
    private String email;
}

This looks exactly like the CustomerEntity without the persistence annotations, right? It sure does. I actually copy/pasted most of that class into this class and removed the annotations. But that’s the point: now my model object doesn’t have those persistence annotations in it and is more useful because of that.

Create the Mapper

Create a package called com.brianrook.medium.customer.dao.mapper. In that package create a java interface called CustomerEntityMapper. In that interface add this content:

package com.brianrook.medium.customer.dao.mapper;

import com.brianrook.medium.customer.dao.entity.CustomerEntity;
import com.brianrook.medium.customer.service.model.Customer;
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;

@Mapper
public interface CustomerEntityMapper {
    CustomerEntityMapper INSTANCE = Mappers.getMapper(CustomerEntityMapper.class);

    CustomerEntity customerToCustomerEntity(Customer customer);
    Customer customerEntityToCustomer(CustomerEntity customerEntity);
}

This is a very simple mapper because the field names in the classes are the same and mapstruct knows how to map fields that are named the same automatically. As you can see we can translate both ways between the model and the entity.

Create the Service

In com.brianrook.medium.customer.servicecreate a java class called CustomerService. In that class add this content:

package com.brianrook.medium.customer.service;

import com.brianrook.medium.customer.dao.CustomerDAO;
import com.brianrook.medium.customer.dao.entity.CustomerEntity;
import com.brianrook.medium.customer.dao.mapper.CustomerEntityMapper;
import com.brianrook.medium.customer.service.model.Customer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class CustomerService {

    @Autowired
    CustomerDAO customerDAO;

    public Customer saveCustomer(Customer customer) {
        return persistCustomer(customer);
    }

    private Customer persistCustomer(Customer customer) {
        CustomerEntity customerEntity = CustomerEntityMapper.INSTANCE.customerToCustomerEntity(customer);
        CustomerEntity storedEntity = customerDAO.save(customerEntity);
        Customer returnCustomer = CustomerEntityMapper.INSTANCE.customerEntityToCustomer(storedEntity);
        return returnCustomer;
    }


}

This is a very simple service but you can see how we’re using mapstruct. I’ve isolated the code that is used for transforming a customer to a customer entity into its own method and tied it to the logic around the CustomerDAO. That makes the saveCustomer method very clean and it would be very easy to add validation logic, message publishing or any other functionality to that service method.

At this point, I probably should write tests, especially around the mapping logic since I’ve already called that out as a very risky bit of code. However, I’m going to save that for the next step. Lets make sure this builds and the tests still pass.

Build and Commit

git checkout -b service
mvn clean install
git add .
git commit -m "Building the service layer"
git push --set-upstream origin service
git checkout master
git merge service
git push

0 comments on “Building a Service LayerAdd yours →

Leave a Reply

Your email address will not be published. Required fields are marked *