DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Related

  • Micronaut vs Spring Boot: A Detailed Comparison
  • High-Performance Reactive REST API and Reactive DB Connection Using Java Spring Boot WebFlux R2DBC Example
  • Using Spring Cloud Gateway and Discovery Service for Seamless Request Routing
  • (Spring) Booting Java To Accept Digital Payments With USDC

Trending

  • Fact-Checking LLM Outputs Programmatically: Building a Verification Layer That Catches Hallucinations
  • When Angular APIs Return 200 but the Frontend Is Already Failing Users
  • Context Is the New Schema
  • How AI Coding Assistants Are Changing Developer Flow
  1. DZone
  2. Software Design and Architecture
  3. Integration
  4. Spring Cloud: How To Implement Service Discovery (Part 2)

Spring Cloud: How To Implement Service Discovery (Part 2)

In this article, explore two different architectural approaches to combine Service Discovery and Remote Configuration.

By 
Mario Casari user avatar
Mario Casari
·
Jun. 16, 23 · Tutorial
Likes (5)
Comment
Save
Tweet
Share
9.0K Views

Join the DZone community and get the full member experience.

Join For Free

In the previous part of this article, Spring Cloud: How to Implement Service Discovery (Part 1),  we saw the basics of Service Discovery in the context of Spring Cloud. We have seen that the Netflix OSS Eureka component is still the main choice. In this post, we are going to discuss some Eureka additional topics, such as:

  • Java client API
  • REST API
  • Secure the discovery server and the client services
  • Combine Service Discovery with Distributed Configuration

Service Discovery: Client Java API 

In the examples in the first part of this article,  the registering and fetching features were running under the hood and we have only seen the results of testing the whole architecture by calling a client REST endpoint. There is also a way to interact with the Eureka API programmatically, by using Java method calls. A possible choice would be to use the EurekaClient class. For example, if we want to get all the instances of a service identified by a particular ID, we could write the following code, supposing we have a client implemented as a Spring Boot application exposing REST services:

Java
 
  	@Autowired
	private EurekaClient eurekaClient;
		
	@GetMapping("/testEurekaClient")
	public String testEurekaClient() {
		Application application = eurekaClient.getApplication("CLIENT-SERVICE");
		List<InstanceInfo> instanceInfos = application.getInstances();
	    if (instanceInfos != null && instanceInfos.size() > 0 ) {
	        return instanceInfos.get(0).getHomePageUrl();
	    }
	    return null;
	}


Spring Cloud offers an alternative to the above with its own DiscoveryClient class. So, we can implement the above code also with the following:   

Java
 
	@Autowired
	private DiscoveryClient discoveryClient;
			         	
	@GetMapping("/testDiscoveryClient")
	public String testDiscoveryClient() {
	    List<ServiceInstance> serviceInstances = discoveryClient.getInstances("CLIENT-SERVICE");
	    if (serviceInstances != null && serviceInstances.size() > 0 ) {
	        return serviceInstances.get(0).getServiceId();
	    }
	    return null;
	}


Service Discovery: Eureka's REST API 

We have seen in the previous section how we can use a Java API to interact with the Eureka discovery server. In a more general scenario, we might have components not written in Java. In that case, we can use a REST API provided by Eureka. Some of its endpoints are described hereafter:

  • /eureka/v2/apps: With GET operation, gets all the instances
  • /eureka/v2/apps/appID: If used with a POST action, it registers an application instance, and with a GET, it fetches all the application IDs.
  • /eureka/v2/apps/appID/instanceID: If used with a DELETE action, it removes the specific instance from the server's registry, whereas a PUT sends a "heartbeat" to the server for that particular instance.
  • /eureka/v2/apps/appID/instanceID/metadata?key=value: With a PUT serves the purpose of updating metadata

Service Discovery: Securing the Server

Putting in place a minimum level of security is important for discovery instances in a production environment. We can protect the discovery server, for instance, by a simple authentication phase based on username and password. To do so, we should set a username and password in the application.yml file by the spring.security.user properties:

YAML
 
spring:
   security:
      user:
         name: myusername
         password: mypassword   


Then, in order to be able to authenticate and communicate with the service discovery server we must set something like the following on the client-side configuration, supposing the server port is 8760 and is running locally along with the client:

YAML
 
eureka:
   client:
      serviceUrl:
         defaultZone: http://myusername:mypassword@localhost:8760/eureka/ 


Service Discovery: Securing the Client

The client side in this discussion is made by the service components of our microservice system. Securing them means securing the communication channels for each component. If the clients expose its features through REST services, the communication channel lies on HTTP protocol. We can secure it by enforcing SSL communication. 

To enable SSL on the client side, we need a certificate in the first place. We can generate a self-signed certificate using the keytool Java program, like this:

Plain Text
 
keytool -genkey -alias client -storetype PKCS12 -keyalg RSA -keysize 2048 -keystore keystore.p12 -validity 365


Then we can take the generated keystore.p12 file and copy it into the /resources folder of our Spring Boot application and set the following piece of configuration in the application.yml file:

YAML
 
server:
   ssl:
      key-store: classpath:keystore.p12
      key-store-password: mypassword
      keyStoreType: PKCS12
      keyAlias: client


By the above, we can call our service using HTTPS in the protocol part of the URL. We can improve the configuration by enabling HTTPS and not HTTP, by setting the following Eureka instance properties:

YAML
 
eureka: 
   instance: 
      securePortEnabled: true 
      nonSecurePortEnabled: false 
      statusPageUrl: https://${eureka.hostname}:${server.port}/info 
      healthCheckUrl: https://${eureka.hostname}:${server.port}/health
      homePageUrl: https://${eureka.hostname}:${server.port}/


Here we have also set some endpoints, like the info, health, and the home page URL.

Service Discovery: Combine With Remote Configuration

We can combine service discovery with distributed configuration in two different ways:

  • Config First approach
  • Discovery First approach

We are going to describe these two approaches in the following sections.

Config First

In the Config First approach, the remote configuration server is the central point of the architecture. Both client and discovery service instances will connect to the config server in order to fetch their configuration.

To set up the configuration server, we have to add this dependency to the Maven POM file: 

XML
 
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-config-server</artifactId>
    </dependency>


In the application.yml file, to simplify the example, we are going to set the spring.profiles.active property as native. This way we can use a local repository, based on configuration files stored in the classpath, and we can choose to put them in a /config subfolder of the /resources Spring Boot application directory. We also secure the server with basic authentication by the security.user property.

YAML
 
spring:
  profiles:
     active: native
  application:
    name: config-server
  security:
    user:
      name: myusername
      password: mypassword


In the application.yml file of the client and discovery service applications, we will store just the basic properties; for instance, those required to connect to the configuration server. The rest of the properties will be stored on the configuration server itself, in specific files stored in the /config folder mentioned above.

Client Base Set Up

For the client service basic configuration, we have to add both the remote configuration and Eureka discovery client dependencies:

XML
 
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-config</artifactId>
    </dependency>   
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
    </dependency>


And in the application.yml file, we will set the config.import parameter with the optional:configserver value, in order to connect to the config server (no need to specify the  http://localhost:8888 URL, since this is the default for the config server):

YAML
 
spring:  
  application:
    name: client-service
  config:
    import: "optional:configserver:"
  cloud:
    config:
      username: myusername
      password: mypassword


Discovery Server Base Set Up

The discovery server also needs the spring-cloud-starter-config dependency:

XML
 
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>


And the application.yml content will be similar to that needed for the client:

YAML
 
spring:  
  application:
    name: discovery-service
  security:
     user:
        name: myusername
        password: mypassword
  config:
    import: "optional:configserver:"
  cloud:
    config:
      username: myusername
      password: mypassword


Specific Configuration for Client and Discovery Services

Having set the basic configuration of client and discovery components as above, we should set the more specific one inside the configuration server. We will use two configuration files stored in the /resources/config folder of the Spring Boot application mentioned before. The files are named after the application.name property of each application:

  • /config/client-service.yml
  • /config/discovery-service.yml

The content of client-service.yml will be:

YAML
 
server:
   port: ${PORT:8080}

myproperty: value

eureka:
  instance:
    hostname: localhost
  client:
    serviceUrl:
       defaultZone: http://myusername:mypassword@localhost:8760/eureka/


As we can see above, the defaultZone property contains the URL of the Eureka server instance, with username and password inside it, since we have secured the discovery server by basic authentication, as the configuration server.

As for the discovery server remote configuration, i.e. the discovery-service.yml file, we will have the following content:

YAML
 
eureka:
   instance:
      hostname: localhost
   client:
      registerWithEureka: false
      fetchRegistry: false
      
server:
   port: ${PORT:8760}


Config First: Sample Projects from GitHub

You can find three sample projects with the above configuration on GitHub:

  • Config Server
  • Client Service
  • Discovery Service

We can test the example by compiling and building the three projects with 'mvn clean install' and then executing the single jar files from the command line:

Plain Text
 
java -jar spring-cloud-discovery-configserver-configfirst-1.0-SNAPSHOT.jar 
... 
java -jar spring-cloud-discovery-client-configfirst-1.0-SNAPSHOT.jar 
...
java -jar spring-cloud-discovery-server-configfirst-1.0-SNAPSHOT.jar


Then we can check the discovery service dashboard, where we will see the single client instance registered as 'CLIENT-SERVICE', and check the client, for instance, by executing some REST service implemented by it.

Discovery First

In the Discovery First approach, the discovery service is not supposed to store its configuration remotely. On the contrary, it is the config server that registers itself on the discovery registry. As for the client service, it will also register itself on the discovery service, in order to fetch the properties needed to connect to the config server. In this architecture, the client needs to know just the application ID of the config server, and the security credentials if there are any.

The discovery service does not need the config server client dependency anymore, and its configuration can be stored entirely in its local application.yml file:

YAML
 
spring:
   application:
      name: discovery-service
   security:
      user:
         name: myusername
         password: mypassword
               
eureka:
   instance:
      hostname: localhost
   client:
      registerWithEureka: false
      fetchRegistry: false
      
server:
   port: ${PORT:8760}


The client and config server local setups are described in the two sections below.

Client Base Set Up

The Maven POM dependencies are the same as the Config First scenario, while the basic local configuration in the local application.yml file must contain the URL to the Eureka service. 

YAML
 
spring:
   application:
      name: client-service
      
eureka:
  client:
     serviceUrl:
        defaultZone: http://myusername:mypassword@localhost:8760/eureka/


Config Server Base Set Up

In addition to the spring-cloud-config-server in the discovery first scenario, the config server application needs also the Eureka client dependency:

XML
 
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-config-server</artifactId>
</dependency>


And the application.yml content will contain the discovery service URL, just like the client component:

YAML
 
server:  
  port: ${PORT:8888}
  
spring:
  profiles:
     active: native
  application:
     name: config-server
           
eureka:
  instance:
    hostname: localhost
  client:
    serviceUrl:
      defaultZone: http://myusername:mypassword@localhost:8760/eureka/


Specific Configuration for Client Service

In the discovery first scenario, the config server local repository will contain only the client configuration file:

  • /config/client-service.yml

And the content of client-service.yml will be:

YAML
 
server:
   port: ${PORT:8080}

myproperty: value

spring:
   cloud: 
      config: 
         discovery: 
            enabled: true 
            serviceId: config-server

eureka:
  client:
    serviceUrl:
       defaultZone: http://myusername:mypassword@localhost:8760/eureka/


Here, besides the discovery service URL, which is needed because the client has to connect first to it, we can also see the spring.cloud.config.discovery.enabled property set to true. This property makes the client application aware that it must connect to the config server by its metadata stored in the discovery service registry. The serviceId property stores the config server application ID which matches that stored in the discovery service registry.

We can conclude that in the discovery service scenario, the config server instance (or instances) can be changed to run in a different host/port without the need to modify the client configuration. 

Discovery First: Sample Projects from GitHub

You can find below the sample projects' GitHub links for the Discovery First scenario:

  • Config Server
  • Client Service
  • Discovery Service

We can test the example as we already did for the config first scenario.

Conclusion

In this post, we have completed the discussion started in the first part of the article and introduced some further topics, such as:

  • Programmatic Java API
  • REST API
  • How to put a basic layer of security on discovery server and client applications
  • Combine discovery features and remote configuration in two different ways: config first and discovery first. 

You can find the source code for the examples in this article in the following GitHub module:

  • Spring Cloud Discovery Examples Source Code
API REST Service discovery Spring Cloud Spring Boot Java (programming language)

Published at DZone with permission of Mario Casari. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Micronaut vs Spring Boot: A Detailed Comparison
  • High-Performance Reactive REST API and Reactive DB Connection Using Java Spring Boot WebFlux R2DBC Example
  • Using Spring Cloud Gateway and Discovery Service for Seamless Request Routing
  • (Spring) Booting Java To Accept Digital Payments With USDC

Partner Resources

×

Comments

The likes didn't load as expected. Please refresh the page and try again.

  • RSS
  • X
  • Facebook

ABOUT US

  • About DZone
  • Support and feedback
  • Community research

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 215
  • Nashville, TN 37211
  • [email protected]

Let's be friends:

  • RSS
  • X
  • Facebook