Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Building Cloud Native Apps With Spring – Part 4

DZone's Guide to

Building Cloud Native Apps With Spring – Part 4

This part of the tutorial series adds a user interface to the cloud application.

· Cloud Zone
Free Resource

Deploy and scale data-rich applications in minutes and with ease. Mesosphere DC/OS includes everything you need to elastically run containerized apps and data services in production.

After completing parts 1, 2, and 3 of this tutorial, we have a basic microservices application setup and running.  It is simple, but incorporates many of the mandates of being a cloud native application, so we are off to a good start.

There are a couple of additional features in Spring Cloud Netflix that are particularly useful and worth demonstrating.  In this blog post I thought we would take a look at Feign.  Rather than describe it myself, the Spring Cloud documentation does a good job

Feign is a declarative web service client. It makes writing web service clients easier. To use Feign create an interface and annotate it. It has pluggable annotation support including Feign annotations and JAX-RS annotations. Feign also supports pluggable encoders and decoders. Spring Cloud adds support for Spring MVC annotations and for using the same HttpMessageConverters used by default in Spring Web. Spring Cloud integrates Ribbon and Eureka to provide a load balanced http client when using Feign.

In summary, if you are looking to build a REST API client, Feign is your friend.  Making REST API calls from one service to another is a very common pattern in microservice applications so this functionality will prove particularly useful.  Also notice that Spring Cloud adds support for Ribbon and Eureka to Feign.  We are already familiar with what Eureka does from the previous parts of this tutorial, but what is Ribbon?

Ribbon is another project from Netflix OSS and is what amounts to a client side load balancer.  This is important for obvious reasons when we start to look at services distributed over different data centers and geographies.  For example, Ribbon will use Eureka to figure out which instance of a service it should make a request to.  We will talk about Ribbon more in a future blog post, for now just know that is will load balance our requests from our Feign clients.

Getting Started With Feign

Now that we know what Feign is and why we want to use it, lets leverage it in our sample application.  Right now in order to get the participants for races we have to make a request to /races to get all the races and then make a request to /participants/races/{id} in order to get the participants for a given race.  In some situations this might be OK, but maybe not for all situations.  Consider the case where our app is being used on a mobile device.  Since the network on a mobile device is typically a lot slower than a desktop we might want to limit the number of network requests we need to make in order to get all the data we need.  In other words a mobile client might want to make a single request to get all the race and participant data instead of multiple requests.  Lets introduce a new API for mobile clients to use that does just that.

Our new API will need to use both the races service and the participants service.  Since it is really returning data about races it makes sense for it to live in the races service.  We need a way to call the participants service from the races service, this is where Feign comes in.  First lets open the POM file for our races service and add the Feign dependency to our project.  In the dependencies section of your POM add the following dependency:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-feign</artifactId>
</dependency>

Now lets open OcrRacesApplication.java in com.ryanjbaxter.spring.cloud.ocr.races.  Lets create a new interface that acts as our REST client for talking to the participants service.  Create a new class called ParticipantsClient with the following code:

@FeignClient("participants")
interface ParticipantsClient {

    @RequestMapping(method = RequestMethod.GET, value="/races/{raceId}")
    List<Participant> getParticipants(@PathVariable("raceId") String raceId);

}

The first thing you notice is that this interface uses an annotation called @FeignClient.  This annotation is telling Feign that we will be talking to a service called “participants”.  Feign will use Eureka to figure out the correct URL for the participants service.  The rest of the interface should look pretty familiar to you if you are familiar with Spring MVC.  The @RequestMapping annotation on the getParticipants method is telling Feign to make a GET request to the participants service at the path /races/raceId.

At this point you will have compile errors in your interface because there is no class called Participant in the races service.  If you are a seasoned Java developer, your first instinct will probably be to do some refactoring.  You might go to the participants service extract out the Participant class into its own project.  Then you would change the participants and races service so they depend on this new project.  This has been engrained in our minds due to the DRY (do not repeat yourself) principal, which says we should not be copying and pasting code all over the place due to the fact that it will become unmaintainable.  This is certainly a valid concern, however we have to balance the DRY principal along with other principals of microservices.  The problem with this approach to refactoring our application is that we now have a common class used by 2 (or more) services.  What happens when one service needs to make a change to that class?  If the change is drastic enough, you can break the other service.  This means that the services can’t evolve independently of each other, which is one of the benefits we are trying to achieve by using microservices.

At the end of the day you have to make a decision that is right for you, your team, and your project.  Do you want to share code between your microservices or do you want the benefit of being able to evolve your services independently of each other?  In this case we will NOT follow the DRY principal and create a new Participant class in our races service.  Why?  Think about how you would be working if you were building a real production grade microservices application.  You would be a developer on a team that is responsible for a single service.  In theory you will know nothing about the implementations of other services you depend on, the only thing you can rely on is their public API.  They may not even be implemented in the same language that you are using.  Based on that logic, it makes sense for you to create a Participant class in your service which corresponds to what their public API will return.  In my opinion, when it comes to microservices, sharing code between services does not generally make sense.

In OcrRacesApplication.java create a new Participant class:

class Participant {
    private String firstName;
    private String lastName;
    private String homeState;
    private String shirtSize;
    private List<String> races;
    public Participant(String firstName, String lastName, String homeState,
            String shirtSize, List<String> races) {
        super();
        this.firstName = firstName;
        this.lastName = lastName;
        this.homeState = homeState;
        this.shirtSize = shirtSize;
        this.races = races;
    }

    public Participant(){}
    public String getFirstName() {
        return firstName;
    }
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    public String getLastName() {
        return lastName;
    }
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    public String getHomeState() {
        return homeState;
    }
    public void setHomeState(String homeState) {
        this.homeState = homeState;
    }
    public String getShirtSize() {
        return shirtSize;
    }
    public void setShirtSize(String shirtSize) {
        this.shirtSize = shirtSize;
    }
    public List<String> getRaces() {
        return races;
    }
    public void setRaces(List<String> races) {
        this.races = races;
    }
}

Finally we will need a new Race class which contains the information about the participants in the race.  Here, again we have a couple of options.  We can add a list of participants to our existing Race class but doing this would result in this weird participants property in the JSON that is always an empty array when we make requests to /races.  It seems odd to me as a consumer to have a property that seemingly never gets used until we call this new API we are creating.  The second option is create a subclass of Race which contains participants.  Add a new class called RacesWithParticipants:

class RaceWithParticipants extends Race {
    private List<Participant> participants;

    public RaceWithParticipants(Race r, List<Participant> participants) {
        super(r.getName(), r.getId(), r.getState(), r.getCity());
        this.participants = participants;
    }

    public List<Participant> getParticipants() {
        return participants;
    }

    public void setParticipants(List<Participant> participants) {
        this.participants = participants;
    }
}

 

Using The Feign Client

Now we are ready to create our new API which will return race data including participant information.  In the OcrRacesApplication class where we have our existing getRaces API create a new API called getRacesWithParticipants.  But before we do that we will need an instance of ParticipantsClient which we will use in the new API to call the participants service.  Add a new variable to the class:

@Autowired
private ParticipantsClient participantsClient;

Now add the new API:

@RequestMapping("/participants")
    public List<RaceWithParticipants> getRacesWithParticipants() {
        List<RaceWithParticipants> returnRaces = new ArrayList<RaceWithParticipants>();
        for(Race r : races) {
            returnRaces.add(new RaceWithParticipants(r, participantsClient.getParticipants(r.getId())));
        }
        return returnRaces;
    }

That is all the code we need to write, now lets test out our new API.  Start the eureka, races, participants, and web services and go to http://localhost:8080/races/participants.  (Make sure you let all the services register themselves with Eureka before trying the API.)  This should return some JSON that looks like this:

[
   {
      "name":"Spartan Beast",
      "id":"123",
      "state":"MA",
      "city":"Boston",
      "participants":[
         {
            "firstName":"Ryan",
            "lastName":"Baxter",
            "homeState":"MA",
            "shirtSize":"S",
            "races":[
               "123",
               "456"
            ]
         }
      ]
   },
   {
      "name":"Tough Mudder RI",
      "id":"456",
      "state":"RI",
      "city":"Providence",
      "participants":[
         {
            "firstName":"Ryan",
            "lastName":"Baxter",
            "homeState":"MA",
            "shirtSize":"S",
            "races":[
               "123",
               "456"
            ]
         },
         {
            "firstName":"Stephanie",
            "lastName":"Baxter",
            "homeState":"MA",
            "shirtSize":"S",
            "races":[
               "456"
            ]
         }
      ]
   }
]

At the same time you can also continue to use the /races API at http://localhost:8080/races to just get the race data.

As you can see Feign makes it very easy to build REST clients for other services in your microservices app.  Just by using a few annotations and minimal Java code you can easily construct a REST client for any service you want.

Discover new technologies simplifying running containers and data services in production with this free eBook by O'Reilly. Courtesy of Mesosphere.

Topics:
cloud ,spring ,spring cloud ,spring boot ,cloud foundry

Published at DZone with permission of Ryan Baxter, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

THE DZONE NEWSLETTER

Dev Resources & Solutions Straight to Your Inbox

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.

X

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}