Setting Up Reservation Client (Edge Service)
Introduction
We are now at our last service to build which is our edge service. Edge Service pattern is widely used in microservices architecture to hide multiple backend services behind one gateway/ front-door. It is similar to the Facade design pattern, however, an edge service not only routes requests to the correct backend services, it usually serves as a point to insert common cross-cutting features such as security (CORS prevention), authentication, and fault tolerance. You will usually also put your React/ Angular SPA in this edge service. Moreover, an edge service is also a logical place to insert API translation or protocol translation requirements.
Zuul is Netflix’s micro proxy solution we will use. It will sit as an intermediary between clients (e.g. smart phones, HTML5 applications) and our service. Our example will only show a client for HTML5 browser.
Setting up Reservation Client’s Configuration
In the existing config’s git:
vim reservation-client.properties
### Add the below to reservation-service.properties
server.port=${PORT:9999}
# define the destination to which the output MessageChannel should be bound
spring.cloud.stream.bindings.output.destination=reservations
# define oauth URL
security.oauth2.resource.userInfoUri=http://localhost:9191/uaa/user
# To avoid sending zipkin spans/ traces via kafka
spring.zipkin.sender.type=web
## End of reservation-client.properties
git add reservation-client.properties
git commit -m "Add reservation-client.properties"
Setting up Reservation Client
-
Go to https://start.spring.io/
-
Type
reservation-clientas the “Artifact” name, addWeb,Lombok,Feign,Zuul,Hystrix,Stream Kafka,Eureka Discovery,Config Client,Cloud OAuth2,HATEOASandZipkin Clientdependencies, and “Generate Project”:
-
Open the project in your favorite Java IDE (mine is IntelliJ)
-
Since we’ll have to have a domain model in the client side as well, again, create a simple entity
ReservationwithidandreservationNameand furnish it with the below Lombok annotations:package com.example.reservationclient; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @AllArgsConstructor @NoArgsConstructor @Data public class Reservation { private Long id; private String reservationName; } -
Define
ReservationReaderinterface which is aFeignClient:package com.example.reservationclient; import org.springframework.cloud.netflix.feign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import java.util.Collection; @FeignClient("reservation-service") interface ReservationReader { @GetMapping("/reservations") Collection<Reservation> read(); }Feign is yet another Java http client developed by Netflix. Its primary selling point is its declarative style which saves a lot of overheads.
-
If we just want a dumb proxy,
@EnableZuulProxywill suffice. However, in this scenario, our API gateway usually needs to do some “work”. Create a class calledReservationApiGatewayRestController:package com.example.reservationclient; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.stream.messaging.Source; import org.springframework.core.ParameterizedTypeReference; import org.springframework.hateoas.Resources; import org.springframework.http.HttpMethod; import org.springframework.http.ResponseEntity; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; import org.springframework.messaging.support.MessageBuilder; 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 org.springframework.web.client.RestTemplate; import java.util.Collection; import java.util.Collections; import java.util.stream.Collectors; @RestController @RequestMapping("/reservations") public class ReservationApiGatewayRestController { private ReservationReader reservationReader; private MessageChannel output; @Autowired public ReservationApiGatewayRestController(ReservationReader reservationReader, Source source) { this.reservationReader = reservationReader; this.output = source.output(); } public Collection<String> fallback() { System.out.println("fallback hit"); return Collections.EMPTY_LIST; } @RequestMapping(method = RequestMethod.GET, value="names") @HystrixCommand(fallbackMethod = "fallback") public Collection<String> getReservationNames() { return this.reservationReader.read() .stream() .map(Reservation::getReservationName) .collect(Collectors.toList()); } @RequestMapping(method = RequestMethod.POST) public void write(@RequestBody Reservation r) { Message<String> msg = MessageBuilder.withPayload(r.getReservationName()).build(); this.output.send(msg); } }- There is a lot happening above. First we declare a RESTful endpoint “reservations”.
- It has the usual Spring constructor injection setup. We need 2 beans: 1)
RestTemplateto relay calls to our backend reservation service AND 2)MessageChannelobtained viaSourcesink to push write messages to our backend reservation-service as well. - We define a fallback method for Hystrix circuit breaker, we name the method “
fallback”. It just returns an empty List. - Next, we define a GET endpoint called “
names” and inside, we use our feign clientreservationReaderto get the reservations from our backend service and massage the data to only return the names. Note that it’s annotated withHystrixCommandwithfallbackMethodcalledfallback(as defined in 3). In case there’s any exception (e.g. our backend service is down), this endpoint will run the fallback method instead of reporting back the ugly exception message. - Finally, we have the POST endpoint for write which just send a message to the
outputchannel.
-
Create a class called
RateLimiterFilterbelow:package com.example.reservationclient; import com.google.common.util.concurrent.RateLimiter; import com.netflix.zuul.ZuulFilter; import com.netflix.zuul.context.RequestContext; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; import org.springframework.core.Ordered; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Component; import javax.servlet.http.HttpServletResponse; @Component class RateLimitingZuulFilter extends ZuulFilter { private RateLimiter rateLimiter = RateLimiter.create(.1); @Override public String filterType() { return FilterConstants.PRE_TYPE; } @Override public int filterOrder() { return Ordered.HIGHEST_PRECEDENCE; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext currentContext = RequestContext.getCurrentContext(); HttpServletResponse response = currentContext.getResponse(); if (!rateLimiter.tryAcquire()) { currentContext.setSendZuulResponse(false); response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value()); } return null; } }NOTE:
- To avoid our API gateways from being bombarded with requests, we usually set up what’s called rate limiters. Basically, requests happening more frequently than a specified range will be bounced back.
- Spring will register all filters as long as it’s annotated
@Componentand extendsZuulFilter - We use Guava’s rate limiter with value
0.1. It means 1 request every 10 seconds. ZuulFilters only filters Zuul’s proxied services (e.g. not our defined RestControllerReservationApiGatewayController)
-
And finally, in
ReservationClientApplication:package com.example.reservationclient; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.client.loadbalancer.LoadBalanced; import org.springframework.cloud.netflix.feign.EnableFeignClients; import org.springframework.cloud.netflix.zuul.EnableZuulProxy; import org.springframework.cloud.stream.annotation.EnableBinding; import org.springframework.context.annotation.Bean; import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer; import org.springframework.web.client.RestTemplate; import org.springframework.cloud.stream.messaging.Source; @SpringBootApplication @EnableResourceServer @EnableBinding(Source.class) @EnableCircuitBreaker @EnableFeignClients @EnableDiscoveryClient @EnableZuulProxy public class ReservationClientApplication { @Bean @LoadBalanced RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { SpringApplication.run(ReservationClientApplication.class, args); } }Let me have a shot as explaining all the annotations above:
@EnableResourceServeris to activate OAuth2 security@EnableBindingis to set up our channel with default nameoutput@EnableCircuitBreakeris to tell hystrix to activate its circuit breaker@EnableFeignClients&@LoadBalancedare to use Feign as our web service client (it’s integrated with Eureka and can provide load-balancing)@EnableDiscoveryClientis again, to register our service to Eureka@EnableZuulProxyis our main annotation to tell Zuul to proxy all services in Eureka in thisreservation-client
-
in
src/main/resources, create a new file calledbootstrap.properties, add the below:# tell config service what is the name of the service and corresponding config to retrieve spring.application.name=reservation-client # tell the location the location of config server spring.cloud.config.uri=http://localhost:8888 -
Make sure you run all the other services in the previous posts (including Kafka). When they are all booted up, run
ReservationClientApplicationand finally our edge service is started! -
Let’s first test our vanilla Zuul proxied services at http://localhost:9999/reservation-service/reservations. You should get an authorized error message, which means our spring security is working correctly:
<oauth> <error_description> Full authentication is required to access this resource </error_description> <error>unauthorized</error> </oauth> -
Open your favorite Terminal/ Git Bash as we’ll need
curl’s help for now onwards to test our services. -
Fire the below curl command to get an oauth token:
curl -X POST -vu android:secret http://localhost:9191/uaa/oauth/token -H "Accept: application/json" -d "password=spring&username=tomy&grant_type=password&scope=openid&client_secret=secret&client_id=android" -
Now use your retrieved token:
curl http://localhost:9999/reservation-service/reservations -H "Authorization: Bearer 02d0ce25-c667-4571-a31c-91b14bc79fd9"NOTE: Change
02d0ce25-c667-4571-a31c-91b14bc79fd9with our own generated token from 13.you should get the below JSON results:
[{"id":1,"reservationName":"TOMY JAYA"},{"id":2,"reservationName":"JOHN DOE"}] -
Now let’s see if our RateLimiter is working, use the same curl command within 10 seconds of the last request and you should not see anything returned.
-
Next, let’s test our API Gateway:
curl http://localhost:9999/reservations/names -H "Authorization: Bearer 02d0ce25-c667-4571-a31c-91b14bc79fd9"you should get the below JSON results:
["TOMY JAYA","JOHN DOE"] -
So far so good, let’s digress and see our Eureka dashboard at http://localhost:8761/. You should see our services properly registered:

-
Let’s test if our reservation-client breaks gracefully as we’ve set up a circuit breaker. You can kill our reservation-service instance then:
curl http://localhost:9999/reservations/names -H "Authorization: Bearer 02d0ce25-c667-4571-a31c-91b14bc79fd9"you should get an empty array
[]. However, if you hit the below:curl http://localhost:9999/reservation-service/reservations -H "Authorization: Bearer 02d0ce25-c667-4571-a31c-91b14bc79fd9"you should see something like the below:
{"timestamp":1515170276976,"status":404,"error":"Not Found","message":"No message available","path":"/reservation-service/reservations"}Now we understand the importance of guarding our clients using circuit breakers.
-
Okay, how about writes? Let’s try to add a new reservation:
curl -X POST -d '{"reservationName": "VALERIE"}' -H "Content-Type: application/json" -H "Authorization: Bearer 02d0ce25-c667-4571-a31c-91b14bc79fd9" http://localhost:9999/reservations -
I’m aware that
reservation-serviceis down. But no problem, we’ve designed for fail-safe writes. Let’s boot up ourreservation-servicenow by runningReservationServiceApplication. -
Wait for a while since Hystrix needs time to flip back the circuit breaker. When we query again:
curl http://localhost:9999/reservations/names -H "Authorization: Bearer 02d0ce25-c667-4571-a31c-91b14bc79fd9"we should get the newly added reservation:
["TOMY JAYA","JOHN DOE","VALERIE"] -
Open our hystrix dashboard at http://localhost:8010/hystrix.html. Put in http://localhost:9999/hystrix.stream in the textbox and click “Monitor”. Now, you should be able to see requests going to our circuit breakers. Play around by killing reservation-service and bringing it up again to see how the graph looks like.

-
Finally, let’s open our zipkin dashboard to see if we can trace our requests

select “reservation-client” and click “Find Traces”, and click on the first result:

we can observe how our request time spent between the
reservation-clientandreservation-service.
Conclusion & Congratulations!
That’s it! Hope you learnt a thing or two from this series. If you have any questions, feel free to ask ‘em in the Disqus forum below.