Developing and implementing µServices

We will use the domain-driven implementation and approach described in the last chapter to implement the µServices using Spring Cloud. Let's revisit the key artifacts:

The Restaurant µService will be exposed to the external world using REST endpoints for consumption. We'll find the following endpoints in the Restaurant µService example. One can add as many endpoints as per the requirements:

Endpoint

GET /v1/restaurants/<Restaurant-Id>

Parameters

Name

Description

Restaurant_Id

Path parameter that represents the unique restaurant associated with this ID

Request

Property

Type

Description

None

  

Response

Property

Type

Description

Restaurant

Restaurant object

Restaurant object that is associated with the given ID

Endpoint

GET /v1/restaurants/

Parameters

Name

Description

None
 
Request

Property

Type

Description

Name

String

Query parameter that represents the name, or substring of the name, of the restaurant

Response

Property

Type

Description

Restaurants

Array of restaurant objects

Returns all the restaurants whose names contain the given name value

Endpoint

POST /v1/restaurants/

Parameters

Name

Description

None

 

Request

Property

Type

Description

Restaurant

Restaurant object

A JSON representation of the restaurant object

Response

Property

Type

Description

Restaurant

Restaurant object

A newly created Restaurant object

Similarly, we can add various endpoints and their implementations. For demonstration purposes, we'll implement the preceding endpoints using Spring Cloud.

The Restaurant Controller uses the @RestController annotation to build the restaurant service endpoints. We have already gone through the details of @RestController in Chapter 2, Setting Up the Development Environment. @RestController is a class-level annotation that is used for resource classes. It is a combination of @Controller and @ResponseBody. It returns the domain object.

As we move forward, I would like to share with you that we are using the v1 prefix on our REST endpoint. That represents the version of the API. I would also like to brief you on the importance of API versioning. Versioning APIs is important, because APIs change over time. Your knowledge and experience improves with time, which leads to changes to your API. A change of API may break existing client integrations.

Therefore, there are various ways of managing API versions. One of these is using the version in path or some use the HTTP header. The HTTP header can be a custom request header or an Accept header to represent the calling API version. Please refer to RESTful Java Patterns and Best Practices by Bhakti Mehta, Packt Publishing, https://www.packtpub.com/application-development/restful-java-patterns-and-best-practices, for more information.

@RestController
@RequestMapping("/v1/restaurants")
public class RestaurantController {

    protected Logger logger = Logger.getLogger(RestaurantController.class.getName());

    protected RestaurantService restaurantService;

    @Autowired
    public RestaurantController(RestaurantService restaurantService) {
        this.restaurantService = restaurantService;
    }

    /**
     * Fetch restaurants with the specified name. A partial case-insensitive
     * match is supported. So <code>http://.../restaurants/rest</code> will find
     * any restaurants with upper or lower case 'rest' in their name.
     *
     * @param name
     * @return A non-null, non-empty collection of restaurants.
     */
    @RequestMapping(method = RequestMethod.GET)
    public ResponseEntity<Collection<Restaurant>> findByName(@RequestParam("name") String name) {
        
logger.info(String.format("restaurant-service findByName() invoked:{} for {} ", restaurantService.getClass().getName(), name));
        name = name.trim().toLowerCase();
        Collection<Restaurant> restaurants;
        try {
            restaurants = restaurantService.findByName(name);
        } catch (Exception ex) {
            logger.log(Level.WARNING, "Exception raised findByName REST Call", ex);
            return new ResponseEntity< Collection< Restaurant>>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
        return restaurants.size() > 0 ? new ResponseEntity< Collection< Restaurant>>(restaurants, HttpStatus.OK)
                : new ResponseEntity< Collection< Restaurant>>(HttpStatus.NO_CONTENT);
    }

    /**
     * Fetch restaurants with the given id.
     * <code>http://.../v1/restaurants/{restaurant_id}</code> will return
     * restaurant with given id.
     *
     * @param retaurant_id
     * @return A non-null, non-empty collection of restaurants.
     */
    @RequestMapping(value = "/{restaurant_id}", method = RequestMethod.GET)
    public ResponseEntity<Entity> findById(@PathVariable("restaurant_id") String id) {

       logger.info(String.format("restaurant-service findById() invoked:{} for {} ", restaurantService.getClass().getName(), id));
        id = id.trim();
        Entity restaurant;
        try {
            restaurant = restaurantService.findById(id);
        } catch (Exception ex) {
            logger.log(Level.SEVERE, "Exception raised findById REST Call", ex);
            return new ResponseEntity<Entity>(HttpStatus.INTERNAL_SERVER_ERROR);
        }
        return restaurant != null ? new ResponseEntity<Entity>(restaurant, HttpStatus.OK)
                : new ResponseEntity<Entity>(HttpStatus.NO_CONTENT);
    }

    /**
     * Add restaurant with the specified information.
     *
     * @param Restaurant
     * @return A non-null restaurant.
     * @throws RestaurantNotFoundException If there are no matches at all.
     */
    @RequestMapping(method = RequestMethod.POST)
    public ResponseEntity<Restaurant> add(@RequestBody RestaurantVO restaurantVO) {

        logger.info(String.format("restaurant-service add() invoked: %s for %s", restaurantService.getClass().getName(), restaurantVO.getName());
        
        Restaurant restaurant = new Restaurant(null, null, null);
        BeanUtils.copyProperties(restaurantVO, restaurant);
        try {
            restaurantService.add(restaurant);
        } catch (Exception ex) {
            logger.log(Level.WARNING, "Exception raised add Restaurant REST Call "+ ex);
            return new ResponseEntity<Restaurant>(HttpStatus.UNPROCESSABLE_ENTITY);
        }
        return new ResponseEntity<Restaurant>(HttpStatus.CREATED);
    }
}

RestaurantController uses RestaurantService. RestaurantService is an interface that defines CRUD and some search operations and is defined as follows:

Now, we can implement the RestaurantService we have just defined. It also extends the BaseService you created in the last chapter. We use @Service Spring annotation to define it as a service:

The RestaurantRepository interface defines two new methods: the containsName and findByName methods. It also extends the Repository interface:

The Repository interface defines three methods: add, remove, and update. It also extends the ReadOnlyRepository interface:

The ReadOnlyRepository interface definition contains the get and getAll methods, which return Boolean values, Entity, and collection of Entity respectively. It is useful if you want to expose only a read-only abstraction of the repository:

Spring framework makes use of the @Repository annotation to define the repository bean that implements the repository. In the case of RestaurantRepository, you can see that a map is used in place of the actual database implementation. This keeps all entities saved in memory only. Therefore, when we start the service, we find only two restaurants in memory. We can use JPA for database persistence. This is the general practice for production-ready implementations:

@Repository("restaurantRepository")
public class InMemRestaurantRepository implements RestaurantRepository<Restaurant, String> {
    private Map<String, Restaurant> entities;

    public InMemRestaurantRepository() {
        entities = new HashMap();
        Restaurant restaurant = new Restaurant("Big-O Restaurant", "1", null);
        entities.put("1", restaurant);
        restaurant = new Restaurant("O Restaurant", "2", null);
        entities.put("2", restaurant);
    }

    @Override
    public boolean containsName(String name) {
        try {
            return this.findByName(name).size() > 0;
        } catch (Exception ex) {
            //Exception Handler
        }
        return false;
    }

    @Override
    public void add(Restaurant entity) {
        entities.put(entity.getId(), entity);
    }

    @Override
    public void remove(String id) {
        if (entities.containsKey(id)) {
            entities.remove(id);
        }
    }

    @Override
    public void update(Restaurant entity) {
        if (entities.containsKey(entity.getId())) {
            entities.put(entity.getId(), entity);
        }
    }

    @Override
    public Collection<Restaurant> findByName(String name) throws Exception {
        Collection<Restaurant> restaurants = new ArrayList();
        int noOfChars = name.length();
        entities.forEach((k, v) -> {
            if (v.getName().toLowerCase().contains(name.subSequence(0, noOfChars))) {
                restaurants.add(v);
            }
        });
        return restaurants;
    }

    @Override
    public boolean contains(String id) {
        throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
    }

    @Override
    public Entity get(String id) {
        return entities.get(id);
    }

    @Override
    public Collection<Restaurant> getAll() {
        return entities.values();
    }
}

The Restaurant entity, which extends BaseEntity, is defined as follows:

The Table entity, which extends BaseEntity, is defined as follows:

The Entity abstract class is defined as follows:

The BaseEntity abstract class is defined as follows. It extends the Entity abstract class:

We can use the RestaurantService implementation to develop the Booking and User services. The User service can offer the endpoint related to the User resource with respect to CRUD operations. The Booking service can offer the endpoints related to the Booking resource with respect to CRUD operations and the availability of table slots. You can find the sample code of these services on the Packt website.

Spring Cloud provides state-of-the-art support to Netflix Eureka, a service registry and discovery tool. All services executed by you get listed and discovered by Eureka service, which it reads from the Eureka client Spring configuration inside your service project.

It needs a Spring Cloud dependency as shown here and a startup class with the @EnableEurekaApplication annotation in pom.xml:

Maven dependency:

Startup class:

The startup class App would run the Eureka service seamlessly by just using the @EnableEurekaApplication class annotation:

Spring configurations:

Eureka Service also needs the following Spring configuration for Eureka Server configuration (src/main/resources/application.yml):

Similar to Eureka Server, each OTRS service should also contain the Eureka Client configuration, so that a connection between Eureka Server and the client can be established. Without this, the registration and discovery of services is not possible.

Eureka Client: your services can use the following spring configuration to configure Eureka Server:

To see how our code works, we need to first build it and then execute it. We'll use Maven clean package to build the service JARs.

Now to execute these service JARs, simply execute the following command from the service home directory:

For example: