DEV Community

loading...

Write Simple Spring Boot for get currency rate

vrnsky profile image Yegor Voronyansky ・5 min read

Within the framework of this article, we will develop an application for receiving exchange rates from the central bank of Russia. The main technologies we will be using will be Java, Spring Boot, Docker, Vue

  1. Let's start by choosing the required method in the API of the central bank, which is allowed to get the current rates 
  2. Next, let's add WSDL processing to the application so that we don't have to do it by hand 
  3. We use Spring WebServices so that our application can skillfully request data from the API 
  4. Add Swagger to the application 
  5. Add some Vue frontend 
  6. Connect Docker  The first thing we need is, of course, to create an application - this can be done using the Spring Initializr or through IntelliJ IDEA.

The API method we'll be using from the central bank is called GetCursOnDateXML. Below you can see its description.

In order not to describe the structure of requests and responses from the API ourselves, we will use plugins that will help turn WSDL into POJO. Listing pom.xml
Let's check that the plugin really works and the classes we need are created - screen.

<plugin> 
  <groupId>org.codehaus.mojo</groupId>
  <artifactId>jaxws-maven-plugin</artifactId>
  <version>2.6</version>
  <executions>
    <execution>
      <id>generatewsdl</id>
      <goals>
        <goal>wsimport</goal>
     </goals>
     <configuration>
        <vmArgs>
          <vmArg>-Djavax.xml.accessExternalSchema=all</vmArg>
          <vmArg>-Djavax.xml.accessExternalDTD=all</vmArg>
        </vmArgs> <wsdlDirectory>${basedir}/src/main/resources/</wsdlDirectory>  
    <args>
       <arg>-b</arg>
       <arg>https://www.w3.org/2001/XMLSchema.xsd</arg> 
    </args>
 <wsdlFiles> <wsdlFile>${basedir}/src/main/resources/dailyinfo.wsdl</wsdlFile>     </wsdlFiles> 
        </configuration> 
      </execution> 
  </executions>
</plugin>
Enter fullscreen mode Exit fullscreen mode

Now let's add a web service that will be responsible for executing the request and response. Listing of creating a web service.

@Service
public class CBRService extends WebServiceTemplate {

     @Value("${cbr.url}")
     private String cbrApiUrl;     


     public List<ValuteCursOnDate> getCurrenciesFromCbr() throws DatatypeConfigurationException {
        final GetCursOnDateXML getCursOnDateXML = new GetCursOnDateXML();
        GregorianCalendar cal = new GregorianCalendar(); 
        cal.setTime(new Date());
        XMLGregorianCalendar xmlGregCal =  DatatypeFactory.newInstance().newXMLGregorianCalendar(cal);
        getCursOnDateXML.setOnDate(xmlGregCal);
        GetCursOnDateXmlResponse response = (GetCursOnDateXmlResponse) marshalSendAndReceive(cbrApiUrl, getCursOnDateXML);
        if (response == null) {   
          throw new IllegalStateException("Could not get response from CBR Service");        
        }
        final List<ValuteCursOnDate> courses = response.getGetCursOnDateXmlResult().getValuteData();
        courses.forEach(course -> course.setName(course.getName().trim()));
        return courses;    
}
    public ValuteCursOnDate getCurrencyFromCbr(String currencyCode) throws DatatypeConfigurationException {
        return getCurrenciesFromCbr()
             .stream()
             .filter(currency -> currencyCode.equals(currency.getChCode()))
             .findFirst().orElseThrow(() -> new RuntimeException("Could not find currency"));    
   }
}
Enter fullscreen mode Exit fullscreen mode

Great, the MVP of the application is almost complete. Now let's add REST controllers so that we can send the received data from the third-party API outside. We will have one controller and two entry points - receiving all currencies, and receiving a specific currency at the moment.

@RestController
@RequestMapping(value = "/api")
@RequiredArgsConstructor
public class CurrencyController {
     private final CurrencyService currencyService;
     private final CBRService cbrService;

     @PostMapping(value = "/getCurrencyFromCbr")
     public List<ValuteCursOnDate> crbCurrencies() throws DatatypeConfigurationException {
        return cbrService.getCurrenciesFromCbr();
    }     
     @PostMapping(value = "/getCurrencyFromCbr/{currency}")     public ValuteCursOnDate crbOn(@PathVariable String currency) throws DatatypeConfigurationException {
        return cbrService.getCurrencyFromCbr(currency);
    }
}
Enter fullscreen mode Exit fullscreen mode

Let's add Swagger to our application so that all consumers of our API know the contracts for interacting with the system.In order to add Swagger, you need to add one dependency and write the configuration code. Let's check that Swagger actually works. However, we need a complete UI. To do this, let's add another dependency and make sure that everything worked correctly.

<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger2</artifactId> 
  <version>2.6.1</version>
  <scope>compile</scope> 
</dependency> 
<dependency> 
 <groupId>io.springfox</groupId> 
 <artifactId>springfox-swagger-ui</artifactId> 
 <version>2.6.1</version> 
 <scope>compile</scope>        
</dependency>
Enter fullscreen mode Exit fullscreen mode

We have an API, but at the moment we can display currencies only in JSON form. Let's add a little frontend in Vue.

<template>
  <div class="container">
    <div class="row">
      <div class="col-2">
      </div>
      <div class="col-8">
        <h1>Currency Rate</h1>
        <span>на {{ today }}</span>
        <table class="table">
          <thead class="table-striped table-bordered">
            <tr>
              <th scope="col">Name</th>
              <th scope="col">Nominal</th>
              <th scope="col">Rate</th>
              <th scope="col">Digital code</th>
              <th scope="col">Alphabet code</th>
            </tr>
          </thead>
          <tbody>
            <tr v-for="currency in currencies" :key="currency.name">
              <td scope="row">{{ currency.name }}</td>
              <td scope="row">{{ currency.nominal }}</td>
              <td scope="row">{{ currency.course }}</td>
              <td scope="row">{{ currency.code }}</td>
              <td scope="row">{{ currency.chCode }}</td>
            </tr>
          </tbody>
        </table>
      </div>
      <div class="col"></div>
    </div>
  </div>
</template>

<script>
import 'bootstrap/dist/css/bootstrap.css'
import 'bootstrap-vue/dist/bootstrap-vue.css'
import CurrencyService from '@/service/CurrencyService'
export default {
  data() {
    return {
      currencies: [],
      today: new Date()
    }
  },
  created() {
    CurrencyService.getCurrencies()
      .then(response => {
        this.currencies = response.data
      })
      .catch(error => {
        console.log(error.response)
      })
  }
}
</script>
Enter fullscreen mode Exit fullscreen mode

Now, we should add service instance for interact with our backend.

import axios from 'axios'

const apiClient = axios.create({
  baseURL: 'http://localhost:9091',
  withCredentials: false,
  headers: {
    Accept: 'application/json',
    'Content-Type': 'application/json'
  }
})

export default {
  getCurrencies() {
    return apiClient.post('/api/getCurrencyFromCbr')
  }
}
Enter fullscreen mode Exit fullscreen mode

We'll also add Docker for our convenience. Our Dockerfile will look like this

FROM adoptopenjdk/openjdk11:latestARG JAR_FILE=/backend/target/*.jar
COPY ${JAR_FILE} /backend/currency-rate-vue-0.0.1-SNAPSHOT.jar
ENTRYPOINT ["java","-jar","/backend/currency-rate-vue-0.0.1-SNAPSHOT.jar
Enter fullscreen mode Exit fullscreen mode

And last, but not least let's write unit test - for check that our app works correctly

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import ru.rbs.currency_vue.dto.CurrencyCreationResponse;
import ru.rbs.currency_vue.model.Currency;
import ru.rbs.currency_vue.model.CurrencyCode;

import java.math.BigDecimal;
import java.time.LocalDateTime;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@AutoConfigureMockMvc
class CurrencyControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Autowired
    private ObjectMapper objectMapper;

    @Test
    public void testWhenWeAskAboutAllCurrenciesShouldCheckThatAllArePresent() throws Exception {
        mockMvc.perform(post("/api/getCurrencyFromCbr"))
                .andExpect(status().isOk())
                .andDo(print());
    }

    @Test
    public void testWhenWeAskAboutCurrencyShouldCheckThatIsPresent() throws Exception {
        mockMvc.perform(post("/api/getCurrencyFromCbr/USD"))
                .andExpect(status().isOk())
                .andExpect(jsonPath("$.name").value("Доллар США"))
                .andExpect(jsonPath("$.nominal").value(1))
                .andExpect(jsonPath("$.course").exists())
                .andExpect(jsonPath("$.code").value("840"))
                .andExpect(jsonPath("$.chCode").value("USD"))
                .andDo(print());
    }

}
Enter fullscreen mode Exit fullscreen mode
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import ru.rbs.currency_vue.dto.ValuteCursOnDate;

import java.util.List;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNull;

@SpringBootTest
class CBRServiceTest {

    @Autowired
    private CBRService cbrService;

    @Test
    public void testWhenWeTryToGetAllCurrenciesCollectionsShouldBeNotEmpty() throws Exception {
        List<ValuteCursOnDate> cursOnDateList = cbrService.getCurrenciesFromCbr();
        assertFalse(cursOnDateList.isEmpty());
    }

    @Test
    public void testWhenWeTryToGetOnlyOneCurrenciesShouldCheckThatWeCanGetIt() throws Exception {
        ValuteCursOnDate rubCursOnDate = cbrService.getCurrencyFromCbr("USD");
        assertNull(rubCursOnDate);
    }
}
Enter fullscreen mode Exit fullscreen mode

That's all for me, I hope you liked the article Write comments and subscribe

Discussion (0)

Forem Open with the Forem app