Kube Cloud Pt3 | Service Interaction via Kubernetes
full course- Kube Cloud Pt3 | Synchronous Service Interaction
- Kube Cloud Pt3 | REST Interaction
- Kube Cloud Pt3 | Health Indicators
Now that we’ve got a new service, we’re going to make it discoverable via kubernetes and call it from the cloud application service.
Enable Kubernetes Features
Let’s start a new branch in our cloud_application
project
$ git checkout -b kube
Switched to a new branch 'kube'
Edit the pom.xml
and add a new dependencies to enable kubernetes functions
<properties>
...
<spring-cloud.version>2021.0.0</spring-cloud.version>
</properties>
...
<!-- REST Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!-- kubernetes -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-fabric8-all</artifactId>
</dependency>
...
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
Here’s what’s going on. Were adding a dependency management clause to allow us to bring in the spring cloud starters. Then we’re using the correct version of spring cloud so that it will be compatible with our spring boot version. Next, we’re adding feign client which will allow us to build a declarative rest client. Finally, we’re adding the fabric 8 framework that allows our spring boot application to interact with kubernetes.
Create the REST Client
Create the following packages under src/main/java
com.bullyrooks.cloud_application.message_generator
com.bullyrooks.cloud_application.message_generator.client
com.bullyrooks.cloud_application.message_generator.client.dto
Now create a class called MessageResponseDTO
in com.bullyrooks.cloud_application.message_generator.client.dto
with this content:
package com.bullyrooks.cloud_application.message_generator.client.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MessageResponseDTO {
private String message;
}
This will be the DTO that receives the response payload from the Message-generator service.
Next make an interface called MessageGeneratorClient
in com.bullyrooks.cloud_application.message_generator.client
. Add this content
package com.bullyrooks.cloud_application.message_generator.client;
import com.bullyrooks.cloud_application.message_generator.client.dto.MessageResponseDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
@FeignClient(name = "message-generator")
public interface MessageGeneratorClient {
@GetMapping(value = "/message",
produces = MediaType.APPLICATION_JSON_VALUE,
consumes = MediaType.APPLICATION_JSON_VALUE)
MessageResponseDTO getMessage();
}
Next, we need to enable the kubernetes and feign client functionality. Edit CloudApplication
in com.bullyrooks.cloud_application
and add these annotations
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class CloudApplication {
Now we want to make sure that we can call the client from our service layer. Update MessageService
with this code
package com.bullyrooks.cloud_application.service;
import com.bullyrooks.cloud_application.message_generator.client.MessageGeneratorClient;
import com.bullyrooks.cloud_application.message_generator.client.dto.MessageResponseDTO;
import com.bullyrooks.cloud_application.repository.MessageRepository;
import com.bullyrooks.cloud_application.repository.document.MessageDocument;
import com.bullyrooks.cloud_application.repository.mapper.MessageDocumentMapper;
import com.bullyrooks.cloud_application.service.model.Message;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
@Slf4j
public class MessageService {
@Autowired
MessageRepository messageRepository;
@Autowired
MessageGeneratorClient messageGeneratorClient;
public Message saveMessage(Message message){
if (StringUtils.isEmpty(message.getMessage())){
log.info("No message, retrieve from message generator");
MessageResponseDTO dto = messageGeneratorClient.getMessage();
message.setMessage(dto.getMessage());
log.info("retrieved message: {}", message.getMessage());
}
MessageDocument msgDoc = MessageDocumentMapper.INSTANCE.modelToDocument(message);
log.info("saving document: {}", msgDoc);
MessageDocument returnDoc = messageRepository.save(msgDoc);
return MessageDocumentMapper.INSTANCE.documentToModel(returnDoc);
}
}
Here we’ve added the autowire for the new client application as well as a call to the message generator service if a message was not passed in.
Testing
Now we’ll update MessageControllerTest
to add some tests for the new behavior
...
@MockBean
MessageGeneratorClient messageGeneratorClient;
...
@Test
void testGetReturnsMessageIfMissing(){
Long userId = 1l;
//given
CreateMessageRequestDTO request = CreateMessageRequestDTO
.builder()
.firstName(faker.name().firstName())
.lastName(faker.name().lastName())
.build();
when(messageGeneratorClient.getMessage()).thenReturn(
MessageResponseDTO.builder()
.message(faker.gameOfThrones().quote())
.build());
//when
RestTemplate restTemplate = new RestTemplate();
String baseUrl = "http://localhost:" + randomServerPort + MESSAGE_PATH;
UriComponents builder = UriComponentsBuilder.fromHttpUrl(baseUrl)
.build();
ResponseEntity<CreateMessageResponseDTO> result = restTemplate.postForEntity(
builder.toUri(), request, CreateMessageResponseDTO.class);
//then
CreateMessageResponseDTO dto = result.getBody();
assertEquals(request.getFirstName(), dto.getFirstName());
assertEquals(request.getLastName(), dto.getLastName());
assertTrue(StringUtils.isNotBlank(dto.getMessage()));
MessageDocument savedDoc = messageRepository.findById(dto.getMessageId()).get();
assertEquals(request.getFirstName(), savedDoc.getFirstName());
assertEquals(request.getLastName(), savedDoc.getLastName());
assertEquals(dto.getMessage(), savedDoc.getMessage());
}
As you can see in this test, we’re not passing in a message
field in the body of the request. We’re also adding a mock that says when the feign client is called, return a new quote. Most of the rest should look familiar except, since we don’t know the message, we’re just testing if we got a non-null response back and confirm that the message that was returned is the same one that is stored in the database.
Go ahead and run the test. It should pass.
Commit and push
git add .
git commit -m "retrieve message from message generator if missing"
git push --set-upstream origin kube
This should successfully build on the feature branch. If it does, go ahead and merge to main to deploy it to okteto
git checkout main
git merge kube
git push
This should again successfully build and you should see it get deployed to okteto.
Manual Test
Lets just double check with postman
And confirm in the okteto logs
Congratulations, you just got two microservices to communicate with each other in a cloud hosted kubernetes environment!
0 comments on “Kube Cloud Pt3 | REST Interaction”Add yours →