Spring Cloud step-by-step (part 1)
Tags: spring, cloud
Published: 2018-06-21 10:40:14

---
title: Spring Cloud step-by-step (part 1)
published: true
description: Transform Spring Web app into cloud native. Begin from sample Spring Web REST API.
tags: spring,cloud
---

This post is a first in the series about Spring Cloud - a set of Spring libraries for building "cloud native" applications. Originally published at [Cloudly.Tech](https://cloudly.tech/blog/spring-cloud-1/)

What are "cloud native" applications? It means applications that have [12 factors](https://12factor.net/).
In short: these are applications that are easy to deploy on available cloud providers, easy to scale up/down, monitor and release (with CI/CD).

I will show you how to start from a simple Spring Web project to create a Spring Cloud application at the end.

The example application which we're going to build is a rest API for renting goods (e.g videos).
It will return price according to a rental plan and duration.

The project is available at [github](https://github.com/piczmar/spring-cloud-rental) with each step as a separate branch.

In this post we will create [spring-cloud-1](https://github.com/piczmar/spring-cloud-rental/tree/spring-cloud-1) branch.

We will first start from standard Spring web application generated with Spring Initializr at [http://start.spring.io](http://start.spring.io)

Then we will keep adding cloud features to it, such as: 
- centralized configuration with [Cloud Config](https://cloud.spring.io/spring-cloud-config/)
- service discovery with [Cloud Discovery](https://spring.io/guides/gs/service-registration-and-discovery/)
- supervision with [Cloud Tracing](https://cloud.spring.io/spring-cloud-sleuth/)
- API gateway with new [Cloud Gateway](http://cloud.spring.io/spring-cloud-gateway/) or older [Zuul](https://spring.io/guides/gs/routing-and-filtering/)

Let's generate a basic Spring web project.

![Generate project](https://thepracticaldev.s3.amazonaws.com/i/zso5r075bhrp8c2ids2d.png)


Then we will add REST controller, at the beginning it will just respond with zero price.
```java
package com.example.cloud.pricingservice.v1;

import com.example.cloud.pricingservice.v1.dto.PriceRequest;
import com.example.cloud.pricingservice.v1.dto.PriceResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import java.math.BigDecimal;

@RestController
@RequestMapping(path = "/v1")
public class PriceController {

    @RequestMapping(path = "/prices", method = RequestMethod.POST)
    public ResponseEntity<PriceResponse> getPriceAmount(@RequestBody PriceRequest priceRequest) {

        return new ResponseEntity<>(new PriceResponse(BigDecimal.ZERO), HttpStatus.OK);
    }

}
```

Start the project with

```
mvn spring-boot:run
```

Now we can test it with curl
```
curl -X POST -H 'Content-Type:application/json' -d '{"plan":"vip", "start": "2011-12-03T10:15:30+01:00","end" : "2011-12-05T12:15:30+01:00"}' http://localhost:8080/v1/prices
```

It would respond with 
```
{"price":0}
```

Let's assume we have a static configuration of daily prices per rental in yaml file, like:
```
plans: 
   BASIC 10
   STANDARD 20
   VIP 50

```

We will replace application.properties with application.yaml as Spring supports both formats, and add the plans there:

```
server:
  port=8080

pricing:
  plan:
    basic: 10
    standard: 20
    vip: 50
```
Personally I find this format more readable than properties, but it's a matter of taste.
You can use properties if you like so.

In order to get these properties as a map in our application we need to write some code:

```java
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;

import java.math.BigDecimal;
import java.util.HashMap;

@Component
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "pricing")
public class PlanConfig {

    private HashMap<String, BigDecimal> plan;

    public HashMap<String, BigDecimal> getPlan() {
        return plan;
    }

    public void setPlan(HashMap<String, BigDecimal> plan) {
        this.plan = plan;
    }

}
```

Now we have a spring-managed bean which we can inject to a service and calculate prices based on it.
Let's create a service class like below:

```java
package com.example.cloud.pricingservice.services;

import com.example.cloud.pricingservice.config.PlanConfig;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;

@Component
public class PriceCalculator {

    private final PlanConfig planConfig;

    public PriceCalculator(PlanConfig planConfig) {
        this.planConfig = planConfig;
    }

    /**
     * Calculates price according to plans. The minimal unit of price calculation is one day.
     * If the rent time overlaps next day the whole price for another day is calculated.
     *
     * @return calculated price
     */
    public BigDecimal calculatePrice(ZonedDateTime rentStartDate, ZonedDateTime rentEndDate, String planName) {
        Assert.notNull("Plan name is required", planName);

        long exactDaysCount = ChronoUnit.DAYS.between(rentStartDate, rentEndDate);
        long daysCount = exactDaysCount + (isOverlappingNextDay(exactDaysCount, rentStartDate, rentEndDate) ? 1 : 0);

        BigDecimal pricePerDay = planConfig.getPlan().getOrDefault(planName.toLowerCase(), BigDecimal.ZERO);
        return pricePerDay.multiply(new BigDecimal(daysCount));
    }

    private boolean isOverlappingNextDay(long exactDaysCount, ZonedDateTime rentStartDate, ZonedDateTime rentEndDate) {
        ZonedDateTime exactEndTime = rentStartDate.plus(exactDaysCount, ChronoUnit.DAYS);
        Duration overlappedDayDuration = Duration.between(exactEndTime, rentEndDate);
        return overlappedDayDuration.getSeconds() > 0;
    }

}
```

Next, let's update our controller and use `calculatePrice` method in it.

```java
@RestController
@RequestMapping(path = "/v1")
public class PriceController {
    private final PriceCalculator priceCalculator;

    public PriceController(PriceCalculator priceCalculator) {
        this.priceCalculator = priceCalculator;
    }

    @RequestMapping(path = "/prices", method = RequestMethod.POST)
    public ResponseEntity<PriceResponse> getPriceAmount(@RequestBody PriceRequest priceRequest) {

        return new ResponseEntity<>(
                new PriceResponse(priceCalculator.calculatePrice(priceRequest.getStart(), priceRequest.getEnd(), priceRequest.getPlan())),
                HttpStatus.OK);
    }

}
```

If we now build and start the application again and execute the same curl command the result should be different depending on how many days the rental would last.
E.g.:

```
curl -X POST -H 'Content-Type:application/json' -d '{"plan":"basic", "start": "2011-12-03T10:15:30+01:00","end" : "2011-12-05T12:15:30+01:00"}' http://localhost:8080/v1/prices
```

returns

```
{"price":30}
```

Now we have a sample REST API implemented with Spring Web and we can work on it in [next post](https://dev.to/piczmar_0/spring-cloud-step-by-step-part-2-12o2) to transform it into Spring Cloud application.