Kube Cloud Pt3 | REST Interaction

Kube Cloud Pt3 | REST Interaction

Kube Cloud Pt3 | Service Interaction via Kubernetes

full course
  1. Kube Cloud Pt3 | Synchronous Service Interaction
  2. Kube Cloud Pt3 | REST Interaction
  3. 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 InteractionAdd yours →

Leave a Reply

Your email address will not be published. Required fields are marked *