Creating a microservice that talks to other microservices.
In Exercise 6 we deployed a microservice that returns a list of cities. In Exercise 7, we deployed a microservice that, given a city, returns the weather for that city. And in Exercise 9, we created a front-end application that queries these two microservices.
There is a glaring inefficiency in this design: the browser first calls city-service
, waits for it to respond, and upon getting that response, calls weather-service
for each of the cities returned. All these remote calls are made over public internet, whose speed is never guaranteed.
To resolve this inefficiency, we will create a single microservice that implements the Transaction Script pattern: it will orchestrate the calls to individual microservices and return the weather for all cities. To do this, we will use [Spring Cloud OpenFeign]. OpenFeign will automatically obtain the URLs of invoked microservices from Spring Cloud Registry, allowing us to build our all-cities-weather-services
microservice without needing to resolve the locations of the constituent microservices.
Note how the code we create in this section is endpoint-agnostic. All we specify is the name of the services we want to invoke in the @FeignClient
annotation. OpenFeign and Spring Cloud Registry then work together behind the scenes to connect our new microservice to the services we've created previously.
- To create our microservice, we will invoke the Spring Initalizer service from the command line:
curl https://start.spring.io/starter.tgz -d type=maven-project -d dependencies=cloud-feign,web,cloud-eureka,cloud-config-client -d baseDir=all-cities-weather-service -d bootVersion=2.7.5 -d javaVersion=17 | tar -xzvf -
-
Navigate to the path
C:\Users\demouser\all-cities-weather-service
to find the all-cities-weather-service
- Navigate to the path
C:\Users\demouser\all-cities-weather-service\src\main\java\com\example\demo
next to theDemoApplication
class, create aWeather.java
class:
package com.example.demo;
public class Weather {
private String city;
private String description;
private String icon;
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getIcon() {
return icon;
}
public void setIcon(String icon) {
this.icon = icon;
}
}
Note: this is the same Weather
class that we created in Exercise 7 when we defined the original weather-service
with one important difference: we no longer annotate the class as a JPA entity for data retrieval.
- Next, in the same location create the
City.java
class. This is the sameCity
class that we created in exercise 6.
package com.example.demo;
public class City {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
- Then, in the same location, create an interface called
CityServiceClient.java
with the following contents. When we run our new service, OpenFeign will automatically provide an implementation for this interface.
package com.example.demo;
import java.util.List;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient("city-service")
public interface CityServiceClient{
@GetMapping("/cities")
List<List<City>> getAllCities();
}
- Create a similar OpenFeign client interface for the weather service, named
WeatherServiceClient.java
.
package com.example.demo;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
@FeignClient("weather-service")
public interface WeatherServiceClient {
@GetMapping("/weather/city")
Weather getWeatherForCity(@RequestParam("name") String cityName);
}
- To enable Spring Cloud to discovery the underlying services and to automatically generate OpenFeign clients, add the annotations @EnableDiscoveryClient and @EnableFeignClients to the
DemoApplication
class (as well as the correspondingimport
statements):
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
- Everything is now in place to implement the
all-cities-weather-service
. Create the classAllCitiesWeatherController.java
as follows:
package com.example.demo;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import com.example.demo.City;
import com.example.demo.CityServiceClient;
import com.example.demo.Weather;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AllCitiesWeatherController {
@Autowired
private CityServiceClient cityServiceClient;
@Autowired
private WeatherServiceClient weatherServiceClient;
@GetMapping("/")
public List<Weather> getAllCitiesWeather(){
Stream<City> allCities = cityServiceClient.getAllCities().stream().flatMap(list -> list.stream());
//Obtain weather for all cities in parallel
List<Weather> allCitiesWeather = allCities.parallel()
.peek(city -> System.out.println("City: >>"+city.getName()+"<<"))
.map(city -> weatherServiceClient.getWeatherForCity(city.getName()))
.collect(Collectors.toList());
return allCitiesWeather;
}
}
- In order to stop the Feign services timing out automatically, open the
C:\Users\demouser\all-cities-weather-service\src\main\resources\
folder and openapplication.properties
file and add:
feign.client.config.default.connectTimeout=160000000
feign.client.config.default.readTimeout=160000000
- As before, create a specific
all-cities-weather-service
application in your Azure Spring Apps instance by running the below mentioned command in git:
az spring app create -n all-cities-weather-service -s azure-spring-apps-lab-DID -g spring-apps-workshop-DID --runtime-version Java_17
Note: Replace the DID with value , you can also find it from Environment details page and run the below given command in Git Bash
- You can now build your "all-cities-weather-service" project and send it to Azure Spring Apps by running the below mentioned commands in git:
💡__Note:__ Open a new instance of Git Bash and login again using
az login
before running the following commands.
cd all-cities-weather-service
./mvnw clean package -DskipTests
az spring app deploy -n all-cities-weather-service -s azure-spring-apps-lab-DID -g spring-apps-workshop-DID --artifact-path target/demo-0.0.1-SNAPSHOT.jar
cd ..
Note: Replace the DID with value , you can also find it from Environment details page and run the below given command in Git Bash
- You can use the gateway created in exercise 8 to access the all-cities-weather-service directly. Open below url
💡__Note:__ the trailing slash (
/
) is not optional.
- gateway URL: https://azure-spring-apps-lab--gateway.azuremicroservices.io
https://<Your gateway URL>/ALL-CITIES-WEATHER-SERVICE/
- You should get the JSON output with the weather for all the cities:
[{"city":"Paris, France","description":"It's always sunny on Azure Spring Apps","icon":"weather-sunny"},
{"city":"London, UK","description":"It's always sunny on Azure Spring Apps","icon":"weather-sunny"}]