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
- Let's start by choosing the required method in the API of the central bank, which is allowed to get the current rates
- Next, let's add WSDL processing to the application so that we don't have to do it by hand
- We use Spring WebServices so that our application can skillfully request data from the API
- Add Swagger to the application
- Add some Vue frontend
- 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>
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"));
}
}
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);
}
}
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>
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>
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')
}
}
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
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());
}
}
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);
}
}
That's all for me, I hope you liked the article Write comments and subscribe
Top comments (0)