Kube Cloud Pt3 | Synchronous Service Interaction

Kube Cloud Pt3 | Synchronous Service 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

In this course I’m going to show you how to make another spring boot microservice (message-generator), deploy it with our first service (cloud-application). I’ll show how cloud-application service can discover message-generator via kubernetes services, call an endpoint in message-generator with a feign based REST client as well as how to enhance our component tests to cover that new interaction. Let’s get started.

Start a New Spring Boot Microservice

You’ll want to repeat the steps in the first part of this course to setup a new microservice. I’m not going to go over them here, but here’s the outline of what is needed:

  • Create a new github repository (message-generator)
  • Create a new spring boot application
    • Make sure to include Spring Web starter
    • Spring Boot DevTools
    • Lombok
  • Create the repository at canister.io
  • Create the github action workflows
    • Feature branch
    • Main branch with deploy
    • Dependabot
  • Add the secrets to build and push the image as well as the helm chart
    • CANISTER_USERNAME
    • CANISTER_PASSWORD
    • CHART_TOKEN
    • KUBECONFIG

Once I got to a place where everything built and deployed from the base spring boot image, I tagged it.

Add a Controller

The goal here is to generate a message to provide to the cloud-application if the user doesn’t supply a message. It’s a bit contrived and not ideally the way I would evolve a service endpoint for cloud-application, but it will do for our demo.

We’re going to need a controller endpoint and tests. Lets write that code.

Update pom.xml to add some dependencies:

    <properties>
        <java.version>11</java.version>
        <org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
        <javafaker.version>1.0.2</javafaker.version>
    </properties>
...
        <!-- mapstruct -->
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>
        <dependency>
            <groupId>org.mapstruct</groupId>
            <artifactId>mapstruct-processor</artifactId>
            <version>${org.mapstruct.version}</version>
        </dependency>
        <dependency>
            <groupId>com.github.javafaker</groupId>
            <artifactId>javafaker</artifactId>
            <version>${javafaker.version}</version>
        </dependency>

First create a service package in src/main/java/com/bullyrooks/messagegenerator/ and add a MessageSerivce.java class with this code:

package com.bullyrooks.messagegenerator.service;

import com.github.javafaker.Faker;
import org.springframework.stereotype.Service;

@Service
public class MessageService {
    Faker faker = new Faker();

    public String getMessage(){
        return faker.gameOfThrones().quote();
    }
}

I’m using the faker library to generate some random text, because that’s what its good for.

Now create a controller package in src/main/java/com/bullyrooks/messagegenerator/ and add a MessageController.java class with this code:

package com.bullyrooks.messagegenerator.controller;

import com.bullyrooks.messagegenerator.controller.dto.MessageResponseDTO;
import com.bullyrooks.messagegenerator.service.MessageService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MessageController {
    @Autowired
    MessageService messageService;

    @GetMapping("/message")
    public MessageResponseDTO getMessage(){

        return MessageResponseDTO.builder()
                .message(messageService.getMessage())
                .build();
    }
}

We’ll need a dto as well. Create a dto package under controller. Create a class called MessageResponseDTO with this content

package com.bullyrooks.messagegenerator.controller.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MessageResponseDTO {

    private String message;
}

Write a Test

Create a controller package in src/test/java/com/bullyrooks/messagegenerator/. Create a class called MessageControllerTest.java with this code

package com.bullyrooks.messagegenerator.controller;

import com.bullyrooks.messagegenerator.controller.dto.MessageResponseDTO;
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.web.client.RestTemplate;

import java.net.URI;
import java.net.URISyntaxException;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Slf4j
@AutoConfigureMockMvc
public class MessageControllerTest {
    @LocalServerPort
    int randomServerPort;

    @Test
    void testAddCustomerSuccess() throws URISyntaxException, JsonProcessingException {
        RestTemplate restTemplate = new RestTemplate();
        String baseUrl = "http://localhost:" + randomServerPort + "/message";
        URI uri = new URI(baseUrl);

        HttpHeaders headers = new HttpHeaders();
        headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);

        ResponseEntity<MessageResponseDTO> result = restTemplate.getForEntity(uri, MessageResponseDTO.class);

        //Verify request succeed
        assertEquals(200, result.getStatusCodeValue());
        MessageResponseDTO response = result.getBody();
        log.info("Test message returned: {}",response.getMessage());
        assertTrue(StringUtils.isNotBlank(response.getMessage()));
    }
}

Now run the test, you should see it pass with some output like this:

2022-01-23 14:44:15.493  INFO 24748 --- [o-auto-1-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-01-23 14:44:15.493  INFO 24748 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
2022-01-23 14:44:15.494  INFO 24748 --- [o-auto-1-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
2022-01-23 14:44:15.657  INFO 24748 --- [           main] c.b.m.controller.MessageControllerTest   : Test message returned: Do the dead frighten you?

Kubernetes Config

Although this will deploy and startup, it will conflict with the port that cloud-application is already running on. We can fix that quickly by updating the values.yaml like this:

service:
  type: NodePort
  port: 8081

  targetPort: 8080

port:
  containerPort: 8081

This is very similar to what would happen if we ran both cloud-application and message-generator locally with the default config (listening on port 8080). You can fix that by modifying the application.yml of one of them by adding this config: If you do that, make sure that the server.port config matches your targetPort in the values.yaml

server:
  port : 8081

Lets go ahead and commit onto main.

$ git add .
warning: LF will be replaced by CRLF in pom.xml.
The file will have its original line endings in your working directory

$ git commit -m "message generating endpoint"
[main 1057d9c] message generating endpoint
 5 files changed, 116 insertions(+)
 create mode 100644 src/main/java/com/bullyrooks/messagegenerator/controller/MessageController.java
Compressing objects: 100% (14/14), done.
Writing objects: 100% (22/22), 2.82 KiB | 963.00 KiB/s, done.
Total 22 (delta 2), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (2/2), completed with 2 local objects.
To github.com-bullyrook:bullyrooks/message-generator.git
   6e91180..1057d9c  main -> main

$ git push

And we should see it build and deploy to Okteto

The build should pass

We should see it deploy from the pipeline

Setting up kubectl configuration
Preparing helm execution
Executing helm
"***" has been added to your repositories
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "***" chart repository
Update Complete. ⎈Happy Helming!⎈
Release "message-generator" has been upgraded. Happy Helming!
NAME: message-generator
LAST DEPLOYED: Sun Jan 23 21:48:24 2022
NAMESPACE: ***
STATUS: deployed
REVISION: 3
NOTES:
1. Get the application URL by running these commands:
  export NODE_PORT=$(kubectl get --namespace *** -o jsonpath="{.spec.ports[0].nodePort}" services message-generator)
  export NODE_IP=$(kubectl get nodes --namespace *** -o jsonpath="{.items[0].status.addresses[0].address}")
  echo http://$NODE_IP:$NODE_PORT

And see it start up in Okteto

And confirm we can hit the endpoint via postman

0 comments on “Kube Cloud Pt3 | Synchronous Service InteractionAdd yours →

Leave a Reply

Your email address will not be published.