Introduction
In this post I’ll run through a very simple Hello World Microservice example (in Java) that covers the main parts of a web type Micro Service. This example composes of:
- A REST Web Service
- A Lookup / Discovery system ( I’ve used Eureka )
- A type of API Gateway / Web Server
Just to be clear Microservices (or at least setting up a full working version of one with these elements) is somewhat complex. But I’ve tried to make it as simple as it can be. The classes have the minimal number of lines of code ( I’ve broken a few best practices to achieve this, so don’t shoot me! The aim is to demonstrate this in a simple way).
If you just want the code straight away grab it here:
https://github.com/louie1711/hello-api-gateway
https://github.com/louie1711/hello-eureka-server
https://github.com/louie1711/hello-rest-service
Process
I created 3 Spring Boot apps using Spring Initialzr , and imported them into Spring Tool Suite 4 (Eclipse with Spring handy stuff in). The names of the Spring Boot Apps are:
- HelloEurekaLookupServer
- HelloWebServerAPIGateway
- HelloWorldService
I’ll go through each in some detail, but here is briefly the why behind them. The main components are as mentioned above.
- We want a Lookup / Discovery system to register our service with ( and allow it to be looked up by service name).
- We’re going to hide the service behind our public facing web server ( clients on the internet will know nothing about our web service, and will not be able to access it directly ). So even though we’re using it as a web server, it also has a API Gateway function. In fact we could easily change this to just be a API Gateway (and have other web servers / services talking to it to get our service).
Process
I’ll go through the code below, but what happens first is the Service registers with the Eureka (discovery / look up system ).
Then in our case requests come in (from web browsers) such as Chrome to our web server (sort of API Gateway). The browser requests don’t know about our Service directly. The Web server (sort of API Gateway) looks up the service by name from Eureka, then hits the end point of the service to return the payload of the web service (i.e. JSON).
HelloEurekaLookupServer
When creating the Spring Boot app for this, add the Eureka Server Dependancy, Spring Web and Spring Dev tools.
Grab the zip file and pull into your Development environment (I’m using Spring Tools Suite 4).
Then create this class ( note I added @EnableEurekaServer annotation ):
package com.example.HelloEurekaLookupServer; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; @EnableEurekaServer @SpringBootApplication public class HelloEurekaLookupServerApplication { public static void main(String[] args) { SpringApplication.run(HelloEurekaLookupServerApplication.class, args); } }
Then add this to the application.properties file:
server.port=8761 eureka.client.register-with-eureka=false eureka.client.fetch-registry=false logging.level.com.netflix.eureka=OFF logging.level.com.netflix.discovery=OFF
Then build and fire it up and right click run Spring Boot app (if using STS). I’m not going to cover building and running only very briefly. I’m assuming readers to be Java Developers.
You can then visit it in your browser to verify its up and working localhost:8761
Where I’ve put the red arrow below, our service will appear there later once registered with Eureka (next step).
And thats it we have Eureka up and running (and hence a way to register services by name and be able to look up these services when we need them).
HelloWorldService
Next. Create a very simple Hello World service, in a separate Spring Boot App (add the Eureka client dependancy and Spring Web and Spring Dev tools. ).
We’ll add 3 files to this (as below), this will register the service’s server by application name and create a very simple web service to GET json data.
package example.microservice.HelloWorldService; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController @EnableEurekaClient public class HelloWorldRESTController { @GetMapping("/helloworldrest") public HelloWorldWrapper hello() { return new HelloWorldWrapper(); } }
package example.microservice.HelloWorldService; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; @SpringBootApplication @EnableDiscoveryClient public class HelloWorldServiceApplication { public static void main(String[] args) { SpringApplication.run(HelloWorldServiceApplication.class, args); } }
package example.microservice.HelloWorldService; public class HelloWorldWrapper { private String message = "I am the hello world object "; public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } }
Then add this to the application.properties for this Spring Boot app (the build and run ), wait 30 seconds or so and visit eureka again to check its registered with it by its name (below). Eureka localhost:8761
spring.application.name=helloworld-rest server.port=8888
Ignore any red warnings from Eureka, out of scope for this tutorial, so long as the service name appears we’re good.
HelloWebServerAPIGateway
Another Spring boot app with dependancies Thymeleaf , Spring Web and Spring Dev tools.
This is a web server and an API Gateway. Typically you might use an API Gateway from a provider eg AWS API Gateway for example, that has lots of functionality built in. For security, routing and so on (common gateway functionality).
We’re using our gateway / web server for security in this case, we don’t want users to know about or have direct access to our web service. And for simplicity and to demonstrate in this tutorial.
So we create another , spring boot app ( only 2 files this time one for the Spring Boot app and one for our controller ).
package example.microservice.HelloWebServerAPIGateway; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class HelloWebServerApiGatewayApplication { public static void main(String[] args) { SpringApplication.run(HelloWebServerApiGatewayApplication.class, args); } }
The controller (below) here’s the good stuff.
Users send requests via Browser eg Chrome etc for a webpage (default port 8080 for Spring Boot app ). http://localhost:8080/hellowebpage
This could perform any API Gateway function here, for simplicity we’ll just do the following:
- Look up the RESt web service by name “HELLOWORLD-REST”
- Store the JSON from this web service in a map
- Pop the content from the web service in our webpage.
package example.microservice.HelloWebServerAPIGateway; import java.net.URL; import java.util.Map; import java.util.logging.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.cloud.client.ServiceInstance; import org.springframework.cloud.client.discovery.DiscoveryClient; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; import com.fasterxml.jackson.databind.ObjectMapper; @Controller public class PublicFacingController { @Autowired private DiscoveryClient discoveryClient; protected Logger logger = Logger.getLogger(PublicFacingController.class.getName()); public static final String HELLO_REST_NAME = "HELLOWORLD-REST"; @GetMapping("/hellowebpage") public String greeting(Model model) { // the user e.g. chrome/firefox etc isnt aware of this url (and couldnt request this anyway as its not publically DNSed ) ServiceInstance service = discoveryClient.getInstances(HELLO_REST_NAME) .stream() .findFirst() .orElseThrow(); Map<String, String> map; try { URL url = new URL(service.getUri().toString() + "/helloworldrest"); // htpp://someip:someport/serviceurl map = new ObjectMapper().readValue(url, Map.class); map.forEach( (k,v) -> logger.info("JSON is : k = "+k+" , v = "+v) ) ; // print out json returned from service }catch(Exception e) { throw new RuntimeException(" there was a json exception reading the url "); } model.addAttribute("json_from_service", map); // stick what we got in view to show us return "hello"; } }
return hello at the end of the file sends our content to a thyme leaf template called hello.html which is returned to users browser (with our content inserted).
References
I was inspired by this great article on Microservices (but found it abit difficult as a Microservice beginner), so I’ve tried to do something similar but easier for the Microservice beginner https://spring.io/blog/2015/07/14/microservices-with-spring
louie171
Thanks for taking the time together to put this very information session for someone trying to understand Microservices. Your documentation was thorough and worked without a glitch. Two comments based on some minor issues I ran into:
1) pom.xml
Should include “spring-cloud-starter-netflix-eureka-server” as a dependency to resolve the use of DiscoveryClient in the PublicFacingController.
2) application.properties
Perhaps not important but I also added below:
spring.application.name=hello-api-gateway
server.port=8080
Thank you TJ, for taking the time to work through the tutorial. And your comments above are very useful, I must add them into my tutorial at some stage.
Hi there,
I build the client application but it showed this error:
Cannot resolve symbol ‘EnableEurekaClient’
Inside my pom.xml file, these lines were added:
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
What’s wrong?
I followed above steps, but it doesn’t work.
I read this SO: https://stackoverflow.com/questions/60349535/what-is-use-of-enableeurekaclient
hence I removed the tag @EnableEurekaClient.
When I build my client, it worked fine…but…Eureka showed this warning:
EMERGENCY! EUREKA MAY BE INCORRECTLY CLAIMING INSTANCES ARE UP WHEN THEY’RE NOT. RENEWALS ARE LESSER THAN THRESHOLD AND HENCE THE INSTANCES ARE NOT BEING EXPIRED JUST TO BE SAFE.
What’s wrong?
I solved my past issues.
Now, the current issue is the following.
It doesn’t detect this package:
import org.springframework.cloud.client.*
Should it be added into our pom.xml? For this gateway project, it is not mentioned this dependency.
Thanks.
hi Abelardo,
Not sure whats the issue. It’s not something I have time to look at currently, due to other commitments.
Let me know how you get on, if you get it fixed. I will try and have a look in the next few weeks.
thanks
Louis
Thanks Louie171! It was fixed! 🙂
At the moment, I am trying to reach out my hello-world service, but it fails.
It’s like it isn’t able to discover my service which is running:
HELLOWORLD-REST n/a (1) (1) UP (1) – 192.168.1.134:helloworld-rest:8888
Solved.
I added import org.springframework.cloud.client.discovery.DiscoveryClient; for solving above issue.
Now, my application returns the following message:
No value present.
I am invoking this endpoint like: http://localhost:8080/hellowebpage <- is this right?
I think your picture needs an arrow from HelloWorldService towards Eureka thanks to:
@EnableDiscoveryClient directive.
Maybe?
When I click on this url UP (1) – 192.168.1.134:HELLOWORLD-REST:8888 the following message is showed:
There was an unexpected error (type=Not Found, status=404).
No message available
Is it expected as normal output?
Could you provide the .properties for the Eureka project?
I think the configuration inside this file is relevant for my issue.
Thanks in advance. Brs.
hi Abelardo,
Sorry I forgot to get back to you !
Did you manage to get everything resolved ?
I dont have any additional code / props files ( other than whats in the git hub repos above , got a new laptop and nuked the old one ).
This tutorial is a gem. Awesome work. True to the words “for Microservice beginner”. Thank you very much for this.
thanks Arvind, glad you found it useful