Below is a common architecture of a microservice system that is implemented using Spring Cloud. In this picture we have a big box called eureka that contains four services, an api gateway, and a config service. Outside the box are clients that want to make requests and get responses from our system. To communicate with our system, those clients have to make request to api gateway. Api gateway will then follow up to the actual service. In the system, we have a centralized configuration that reside in the config service. Those three type of service (api gateway, micro services, config service) are registered in Eureka so that they can communicate easily. We have learned about Eureka in the previous post. Let's now implement all of this architecture using Spring Cloud.
Common Things
Each of the service above is implemented as a Spring Boot project. Below is how the projects look like in Eclipse. Here we have five projects. As the name implies, config-server, eureka-server, and gateway-server are self explanatory. We also have two eureka clients that will act as a service serving different purpose.
Each project shares the following maven pom.xml content. The spring-boot-starter-parent version is 2.4.4. They have dependency management to spring-cloud-dependencies version 2020.0.2. The other dependencies varies to different project.
Let's now take a look eureka-server project.
Eureka Server
The structure of the eureka-server project is shown below.
In the pom.xml we add dependency to spring-cloud-starter-netflix-eureka-server.
The Spring Boot application is shown below. Here we annotate it with @EnableEurekaServer to make it a Eureka server.
In application.properties file we change the server.port. We also set eureka.client.register-with-eureka and eureka.client.fetch-registry to false to prevent this application from registering itself. We also turned some logging off.
Next we'll see the config-server project.
Config Server
The structure of the config-server project is shown below.
In the maven pom.xml we add dependencies to spring-cloud-starter-netflix-eureka-client since this config-server project will be registered in Eureka. We also add spring-cloud-config-server since this project will act as config server. Lastly, we also add spring-boot-starter dependency.
In the Spring Boot application, we annotate it with @EnableConfigServer.
We also add several value in application.properties file. First, we set server.port and spring.application.name. We also set the Eureka server url through eureka.client.service-url.defaultZone. The next two properties spring.profiles.active and spring.cloud.config.server.native.searchLocations is used to locate configuration files. Here we say to Spring that we put configuration file in
shared folder under project root folder.
Under shared folder we have one application.properties file that will be used by all services connecting to this config-server project. Here we specify the eureka.client.service-url.defaultZone and eureka.instance.hostname since they are common to all services.
Next we'll visit the actual services.
MicroServices
For simplicity and clarity we create two services here. Below is the project structure.
The maven pom.xml files have dependency to the following artifacts: spring-cloud-starter-netflix-eureka-client since these sevices will be registered under eureka. We also add dependency to spring-cloud-starter-config and spring-cloud-starter-bootstrap since we want these services to connect and fetch configuration from our config-server. Lastly we add spring-boot-starter-web dependency.
Next, we'll see the Spring Boot application class. Both classes are simple Spring Boot application annotated with @SpringBootApplication.
The eureka-client project also has a controller ServiceInstanceRestController. Requests to this controller is mapped to /client. The controller also has one method mapped to path variable /{applicationName}. The path variable {applicationName} could be any spring.application.name of projects registered in Eureka. We can call this service from browser using /client/eureka-client or /client/config-server to get information about the requested project.
The eureka-client-greeting project has one controller ServiceInstanceRestController. Requests to this controller is mapped to /greeting. The controller also has one method mapped to path variable /{greeting}. We can call this service from browser using /greeting/morning or /greeting/night and the result string will be returned and printed out to browser.
Next we'll see the application.properties file for both project. Here we can see that eureka-client project uses server.port 8181 and eureka-client-greeting uses server.port 8182. They also set the spring.application.name. This is the name that will be used when communicating to each other in eureka server.
Lastly, each service has a bootstrap.yml that contains url information of config-server. As we've seen above, the config-server listens to port 9898. This bootstrap.yml is called before application.properties so it is a good place to fetch data from config-server before the service starts.
Now we'll see the last part of puzzle, the api gateway.
API Gateway
The project structure of gateway-server project is shown below.
In the maven pom.xml we add dependencies to spring-cloud-starter-netflix-eureka-client since this api gateway will be registered under eureka server. We also add dependency to spring-cloud-starter-gateway itself. We also add dependency to spring-cloud-starter-config and spring-cloud-starter-bootstrap since we want api gateway to connect and fetch configuration from our config-server. We also add dependency to spring-cloud-starter-netflix-hystrix and spring-cloud-starter-circuitbreaker-reactor-resillience4j to have circuit breaker mechanism which we'll see later. Lastly we add dependency to spring-boot-starter. We use hystrix version 2.2.7.RELEASE for this project.
The Spring Boot application is just a simple class annotated with @SpringBootApplication.
We also create a fallback controller FallbackController which we'll use to display a message when a service is unavailable. Here we have two request mapping /client-fallback and /greeting-fallback that returns a message informing service unavailability. We'll see this request mapping later on the filters part of spring.cloud.gateway.routes in application.yml file.
Next we'll see the application.properties file. Here we set value for server.port and spring.application.name as we've done so far for all other projects. We also set value of management.endpoints.web.exposure.include and hystrix.command.fallbackcmd.execution.isolation.thread.timeoutInMilliseconds as they are needed by hystryx.
Api gateway also has bootstrap.xml since it connects to config-server project.
Lastly it has application.yml where we can configure routes for our gateway. As shown below, there are two routes defined by id
client and
client-greeting.
The url for client is lb://eureka-client where lb means load balancer and eureka-client is the spring.application.name of the service. The predicates path means all request path started with /client/ should be redirected to eureka-client service. Note that we created request mapping /client in eureka-client project which match the predicate. In the filters we add CircuitBreaker and supply it with argument name client-fallback and fallbackuri forward:/client-fallback. Note that the name could be any name while fallbackuri is the request mapping of our FallbackController.
The url for client-greeting is lb://eureka-client-greeting where lb means load balancer and eureka-client-greeting is the spring.application.name of the service. The predicates path means all request path started with /greeting/ should be redirected to eureka-client-greeting service. Note that we created request mapping /greeting in eureka-client-greeting project which match the predicate. In the filters we add CircuitBreaker and supply it with argument name greeting-fallback and fallbackuri forward:/greeting-fallback. Note that the name could be any name while fallbackuri is the request mapping of our FallbackController.
Now let's run all of this stuff and see if it works. Run the following projects in order: eureka-server, config-server, gateway-server. Go to http://localhost:9876/ and check if our config-server and gateway-server is registered with eureka.
To check if fallback controller works, make request to gateway server http://localhost:9899/client/config-server. Here we ask for eureka-client project which is not up yet for now. The following error message will be displayed.
Same thing goes for eureka-client-greeting accessed from gateway http://localhost:9899/greeting/morning. Since the service is not up yet, error message will be shown instead.
Let's now run the eureka-client and eureka-client-greeting service. Check if they are registered with eureka as shown below.
Now refresh http://localhost:9899/client/config-server and http://localhost:9899/greeting/morning.