Building Mancala Game in Microservices Using Spring Boot (Part 3: Web Client Microservice Implementation Using Vaadin)

Mancala game

Mancala is for everyone! 


In the first article "Building Mancala Game in Microservices Using Spring Boot (Part 1: Solution Architecture)", I explained the overall architecture of the solution taken for implementing the Mancala game using the Microservices approach.

Then, I discussed the detail implementation of 'mancala-api' microservice in a separate article: Building Mancala Game in Microservices using Spring Boot (Part 2: Mancala API Implementation).

Now, in this article, I am going to discuss the detail implementation of "mancala-web" microservice which is a simple web application developed for this game based on Spring Boot and Vaadin.

This article is step by step guide for implementing the Mancala web interface microservice using a modern web framework called Vaadin. The output would like this:

Image title

The complete source code for this project is available in my GitHub repository.

To build and run this application, please follow the instructions I have provided here.

1— Spring Initializer

The same as the previous project, we start by going to start.spring.io:

Image title

Fill the project information as below:

Group: com.dzone.mancala.game

Artifact: mancala-web

Dependencies:

Image title

Image title


2 — MancalaWebApplication

The same as 'mancala-api' project, we are using Spring Cloud Consul Discovery API to make our microservice discoverable for Consul service registry server. To enable this feature, we have already added below dependency into our pom.xml file:


<!-- Consul  -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>


Then we add @EnableDiscoveryClient to our Spring boot application:

@SpringBootApplication
@EnableDiscoveryClient
@RibbonClient("mancala-service")
public class MancalaWebApplication {

public static void main(String[] args) {
SpringApplication.run(MancalaWebApplication.class);
}

@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}


We will need to provide Consul configurations within the application.properties file:

#consul configurations
spring.cloud.consul.host=consul
spring.cloud.consul.port=8500
spring.cloud.consul.discovery.preferIpAddress=true
spring.cloud.consul.discovery.instanceId=${spring.application.name}:${spring.application.instance_id:${random.value}}
spring.cloud.consul.ribbon.enabled=true


We also need to add the following configuration in our bootstrap.properties file:

spring.application.name=mancala-web



3 — Ribbon Client Load Balancing

Ribbon, is a client-side load balancer provided by Netflix that gives a lot of control over the behavior of HTTP and TCP clients:

Spring boot provides integration of Ribbon implementation for your Spring boot application by adding below dependency into your pom.xml file:

<!-- Ribbon   -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>

and add @RibbonClient annotation to your Spring Boot application startup class as depicted here:

@SpringBootApplication
@EnableDiscoveryClient
@RibbonClient("mancala-service")
public class MancalaWebApplication {

public static void main(String[] args) {
SpringApplication.run(MancalaWebApplication.class);
}

@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

And we need to provide a name for our ribbon client e.g. 'mancala-service' because Ribbon is a named client. Each load balancer is part of an ensemble of components that work together to contact a remote server on demand, and the ensemble has a name that we give it as an application developer. You can also customize Ribbon client based on various configurations. See the documentation.

Now, we can use LoadBalancerClient interface as below:

@Component
public class MancalaClientConfig {

    private LoadBalancerClient loadBalancer;

    @Value("${mancala.api.service.id}")
    private final String apiServiceId = "mancala-api";

    @Autowired
    public void setLoadBalancer(LoadBalancerClient loadBalancer) {
        this.loadBalancer = loadBalancer;
    }

    public String getNewMancalaGameUrl(){
        ServiceInstance instance = this.loadBalancer.choose(apiServiceId);

        String url = String.format("http://%s:%s/games", instance.getHost(), instance.getPort());

        return url;
    }

    public String getSowMancalaGameUrl(String gameId, Integer pitIndex){
        ServiceInstance instance = this.loadBalancer.choose(apiServiceId);

        String url = String.format("http://%s:%s/games/%s/pits/%s", instance.getHost(), instance.getPort(), gameId, pitIndex);

        return url;
    }
}


The LoadBalancerClient.choose('service-id') returns a ServiceInstance based on the availability of instances within our Consul Service Registry. The default algorithm is round-robin. In this application, you can add more instances for our 'mancala-api' service by running below shell command using Docker-compose:

docker-compose scale mancala-api=3



4 — MVC implementation of Web Client Using Vaadin

Vaadin is an open-source web framework that helps Java developers build great user experiences with minimal effort. This tutorial is not intended to go into details of this framework. For more information, please visit the vaadin.com website.

We have used the Model View Controller (MVC) approach for implementing our web client application. It helps us to separate the logic used for each layer: model, view and controller.

Model Classes

Below are model classes we defined within the 'mancala-api' project and we are using them in this project too.

KalahaConstants

This class includes all the constants we have defined for this game:

public class KalahaConstants {
    public static final int emptyStone = 0;
    public static final int leftPitHouseId = 14;
    public static final int rightPitHouseId = 7;

    public static final int firstPitPlayerA = 1;
    public static final int secondPitPlayerA = 2;
    public static final int thirdPitPlayerA = 3;
    public static final int forthPitPlayerA = 4;
    public static final int fifthPitPlayerA = 5;
    public static final int sixthPitPlayerA = 6;

    public static final int firstPitPlayerB = 8;
    public static final int secondPitPlayerB = 9;
    public static final int thirdPitPlayerB = 10;
    public static final int forthPitPlayerB = 11;
    public static final int fifthPitPlayerB = 12;
    public static final int sixthPitPlayerB = 13;

}


MancalaGame

@Data
public class KalahaGame {
    private String id;
    private String playerTurn;
    private List<KalahaPit> pits;

    @JsonIgnore
    public Integer getPlayerAStones(){
        return getPit(firstPitPlayerA).getStones() +
                getPit(secondPitPlayerA).getStones() +
                getPit(thirdPitPlayerA).getStones()+
                getPit(forthPitPlayerA).getStones()+
                getPit(fifthPitPlayerA).getStones()+
                getPit(sixthPitPlayerA).getStones();
    }

    @JsonIgnore
    public Integer getPlayerBStones() {
        return getPit(firstPitPlayerB).getStones() +
                getPit(secondPitPlayerB).getStones() +
                getPit(thirdPitPlayerB).getStones()+
                getPit(forthPitPlayerB).getStones()+
                getPit(fifthPitPlayerB).getStones()+
                getPit(sixthPitPlayerB).getStones();
    }

    public KalahaPit getPit (Integer pitIndex){
        return this.pits.stream().filter(p -> p.getId() == pitIndex).findAny().get();
    }

    @JsonIgnore
    public Integer getLeftHouseStones (){
        return getPit(leftPitHouseId).getStones();
    }

    @JsonIgnore
    public Integer getRightHouseStones (){
        return getPit(rightPitHouseId).getStones();
    }

    @JsonIgnore
    public Integer getPitStones (Integer pitIndex){
        return getPit(pitIndex).getStones();
    }
}


KalahaPit

@AllArgsConstructor
@NoArgsConstructor
@Data
public class KalahaPit implements Serializable {

    private Integer id;
    private Integer stones;

    @JsonIgnore
    public Boolean isEmpty (){
        return this.stones == 0;
    }

    public void clear (){
        this.stones = 0;
    }

    public void sow () {
        this.stones++;
    }

    public void addStones (Integer stones){
        this.stones+= stones;
    }

    @Override
    public String toString() {
        return  id.toString() +
                ":" +
                stones.toString() ;
    }
}


KalahaHouse

public class KalahaHouse extends KalahaPit {
    public KalahaHouse(Integer id) {
        super(id , emptyStone);
    }
}


Controller Classes

The controller is responsible for intercepting user requests and delegates them to the appropriate backend API and provides a response to the client. We have below classed defined for this purpose:

MancalaClient

This class performs all REST call invocations to 'mancala-api' microservice based on Ribbon client load balancer implementation provided in MancalaClientConfig class:

@Component
@Slf4j
public class MancalaClient {

    private RestTemplate restTemplate;

    @Autowired
    private MancalaClientConfig mancalaClientConfig;

    public MancalaClient(@Autowired RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    public KalahaGame startNewMancalaGame() throws Exception {

        String url = mancalaClientConfig.getNewMancalaGameUrl();

        log.info("calling:" + url);

        ResponseEntity<KalahaGame> gameResponse = this.restTemplate.postForEntity(url, null, KalahaGame.class);

        log.info("response: " + new ObjectMapper().writerWithDefaultPrettyPrinter().
                writeValueAsString(gameResponse.getBody()));

        return gameResponse.getBody();
    }

    public KalahaGame sowMancalaGame(String gameId, Integer pitIndex) throws Exception {

        String url = mancalaClientConfig.getSowMancalaGameUrl(gameId, pitIndex);

        log.info("calling: " + url);

        ResponseEntity<KalahaGame> response = restTemplate.exchange(url, HttpMethod.PUT, null, KalahaGame.class);

        log.info("response: " + new ObjectMapper().writerWithDefaultPrettyPrinter().
                writeValueAsString(response.getBody()));

        return response.getBody();
    }
}


GameController

This class acts as the main controller class within MVC pattern that interacts with Vaadin user interface from one side and invokes the corresponding REST-API provided by 'mancala-api' microservice through MancalaClient class from the other side:

@Component
public class GameController {

    private KalahaGame game;

    @Autowired
    private MancalaClient mancalaClient;

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    public KalahaGame startNewGame() throws ApiConnectionException {
        try {
            this.game = mancalaClient.startNewMancalaGame();

            return this.game;

        }catch (Exception e){
            throw new ApiConnectionException("Error connecting to Mancala service!");
        }
    }

    public void sow(Integer pitIndex) throws ApiConnectionException {
        try {
            this.game = mancalaClient.sowMancalaGame(this.game.getId(), pitIndex);

            this.eventPublisher.publishEvent(new SowEvent(this, this.game, pitIndex));

        }catch (Exception ex){
            throw new ApiConnectionException("Error connecting to Mancala service!");
        }
    }

    public boolean hasGameStarted () {
        return this.game != null;
    }
}


View Classes

Building UI classes in Vaadin is very straightforward. It's quite similar to Java Swings API. We will need to build our games layout first and place our UI Components within those layouts. Within Vaadin, UI components are placed within two different layouts: Vertical Layouts and Horizontal Layouts.

To add Vaadin to our project, all we need is to add below dependency into our pom.xml file:

<!-- vaadin UI -->
<dependency>
  <groupId>com.vaadin</groupId>
  <artifactId>vaadin-spring-boot-starter</artifactId>
</dependency>


Here is what I have designed as a simple approach towards proof of concept for my microservice implementation:

Image title

PitComponent Class

this class represents a particular pit within the Mancala game. It consists of two internal Vaadin components: TextField and Button.

@UIScope
@SpringComponent
@Getter
@Slf4j
public class PitComponent extends VerticalLayout {

    private static final Integer defaultPitStones = 0;

    private final TextField pit = new TextField();
    private final Button btn = new Button();

    private GameController gameController;

    public PitComponent() {
        pit.getElement().setAttribute("theme", "align-center");
        pit.setReadOnly(true);
        pit.setValue(defaultPitStones.toString());
        pit.getStyle().set("font-size", "15px");
        pit.getStyle().set("font-weight", "bold");
        pit.setMaxLength(30);
        pit.setMinLength(30);
        btn.getElement().setAttribute("theme", "align-center");
        add(btn, pit);
        setAlignItems(Alignment.CENTER);

        pit.addValueChangeListener(e -> {
            pit.getStyle().set("background-color", "#ff9933");
            new ChangeColorThread(UI.getCurrent(), pit).start();
        });
    }

    private static class ChangeColorThread extends Thread{

        private UI ui;
        private TextField textField;
        public ChangeColorThread(UI ui, TextField textField) {
            this.ui = ui;
            this.textField = textField;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {}
            ui.access(() -> {
                textField.getStyle().set("background-color", "#ffffff");
            });
        }
    }

    public PitComponent(Integer pitIndex, GameController gameController) {
        this();
        this.gameController = gameController;
        pit.setId(pitIndex.toString());

        btn.setText(pitIndex.toString());
        btn.setTabIndex(pitIndex);
        btn.addClickListener(e -> {
            if (!this.gameController.hasGameStarted()){
                Notification.show("Please click on 'Start Game' button to start the game first!");
                return;
            }

            Notification.show(e.getSource().getTabIndex() + " Clicked");
            try {
                this.gameController.sow(e.getSource().getTabIndex());
            } catch (ApiConnectionException ex) {
                log.error(ex.getMessage(), ex);
                Notification.show("Error connecting to the server!. Try later");
            }
        });
    }

    public void setStones(String stones) {
        this.pit.setValue(stones);
    }
}


As you can see, LeftHouse (14), RightHouse (7), and all Pits components (1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13) are instances of PitComponent class.

PitLayoutComponent Class

PitLayoutComponent is a Horizontal Layout designed to include all pits components.


@SpringComponent
@UIScope
public class PitLayoutComponent extends VerticalLayout implements KeyNotifier {

    private PitComponent pit1 ;
    private PitComponent pit2 ;
    private PitComponent pit3 ;
    private PitComponent pit4 ;
    private PitComponent pit5 ;
    private PitComponent pit6 ;

    private PitComponent pit8 ;
    private PitComponent pit9 ;
    private PitComponent pit10;
    private PitComponent pit11 ;
    private PitComponent pit12 ;
    private PitComponent pit13 ;

    public PitLayoutComponent(GameController gameController) {
        this.pit1 = new PitComponent(firstPitPlayerA, gameController);
        this.pit2 = new PitComponent(secondPitPlayerA, gameController);
        this.pit3 = new PitComponent(thirdPitPlayerA, gameController);
        this.pit4 = new PitComponent(forthPitPlayerA, gameController);
        this.pit5 = new PitComponent(fifthPitPlayerA, gameController);
        this.pit6 = new PitComponent(sixthPitPlayerA, gameController);

        this.pit8 = new PitComponent(firstPitPlayerB, gameController);
        this.pit9 = new PitComponent(secondPitPlayerB, gameController);
        this.pit10 = new PitComponent(thirdPitPlayerB, gameController);
        this.pit11 = new PitComponent(forthPitPlayerB, gameController);
        this.pit12 = new PitComponent(fifthPitPlayerB, gameController);
        this.pit13 = new PitComponent(sixthPitPlayerB, gameController);
        HorizontalLayout playerAPits = new HorizontalLayout(pit1, pit2, pit3, pit4, pit5, pit6);
        HorizontalLayout playerBPits = new HorizontalLayout(pit13, pit12, pit11, pit10, pit9, pit8);

        add(playerBPits, playerAPits);
    }

    public void fillPitStones (KalahaGame game){
        this.pit1.setStones(game.getPitStones(firstPitPlayerA).toString());
        this.pit2.setStones(game.getPitStones(secondPitPlayerA).toString());
        this.pit3.setStones(game.getPitStones(thirdPitPlayerA).toString());
        this.pit4.setStones(game.getPitStones(forthPitPlayerA).toString());
        this.pit5.setStones(game.getPitStones(fifthPitPlayerA).toString());
        this.pit6.setStones(game.getPitStones(sixthPitPlayerA).toString());
        this.pit8.setStones(game.getPitStones(firstPitPlayerB).toString());
        this.pit9.setStones(game.getPitStones(secondPitPlayerB).toString());
        this.pit10.setStones(game.getPitStones(thirdPitPlayerB).toString());
        this.pit11.setStones(game.getPitStones(forthPitPlayerB).toString());
        this.pit12.setStones(game.getPitStones(fifthPitPlayerB).toString());
        this.pit13.setStones(game.getPitStones(sixthPitPlayerB).toString());
    }
}


KalahaGameComponent Class

KalahaGameComponent is a Horizontal Layout designed to include the three main layouts of the game including PlayerTurnLayout, GameLayout, and ActionsLayout.


@UIScope
@SpringComponent
@Slf4j
@Component
public class KalahaGameComponent extends VerticalLayout implements KeyNotifier {

    private final PitLayoutComponent pitLayoutComponent;

    private final PitComponent rightHouse ;
    private final PitComponent leftHouse ;

    final Label playerTurnLabel;
    final TextField playerTurnTextField;

    final Label winLabel;


    public KalahaGameComponent(PitLayoutComponent pitLayoutComponent, @Autowired GameController gameController) {
        this.pitLayoutComponent = pitLayoutComponent;

        this.playerTurnLabel = new Label("Player turn:");
        this.playerTurnTextField = new TextField("");
        this.playerTurnTextField.setReadOnly(true);

        // build layout for game information
        HorizontalLayout turnLayout = new HorizontalLayout(playerTurnLabel, playerTurnTextField);
        turnLayout.setAlignItems(Alignment.CENTER);
        add(turnLayout);

        rightHouse = new PitComponent(7 , gameController);
        rightHouse.setAlignItems(Alignment.CENTER);
        rightHouse.add(new Label("Player A"));

        leftHouse = new PitComponent(14, gameController);
        leftHouse.setAlignItems(Alignment.CENTER);
        leftHouse.add(new Label("Player B"));

        HorizontalLayout gameLayout = new HorizontalLayout(leftHouse, pitLayoutComponent, rightHouse);
        gameLayout.setAlignItems(Alignment.CENTER);

        add(gameLayout);

        // Adding the win layout
        winLabel = new Label("");
        winLabel.setVisible(false);
        winLabel.getStyle().set("font-size", "50px");
        winLabel.getStyle().set("color", "#ff0000");

        HorizontalLayout winLayout = new HorizontalLayout(winLabel);
        winLayout.setAlignItems(Alignment.CENTER);

        add(winLayout);

        setAlignItems(Alignment.CENTER);
    }

    public TextField getPlayerTurnTextField() {
        return playerTurnTextField;
    }

    public void fillMancala(KalahaGame game) {
        this.leftHouse.setStones(game.getLeftHouseStones().toString());
        this.rightHouse.setStones(game.getRightHouseStones().toString());
        this.pitLayoutComponent.fillPitStones(game);
    }

    public void newGame(KalahaGame game) {
        this.fillMancala(game);
        this.winLabel.setVisible(false);
    }

    @EventListener
    public void handleFlushEvent (SowEvent event) {
        KalahaGame game = event.getGame();
        this.fillMancala(game);
        this.playerTurnTextField.setValue(event.getGame().getPlayerTurn());

        Integer playerARemainingStones = game.getPlayerAStones();
        Integer playerBRemainingStones = game.getPlayerBStones();

        if (playerARemainingStones == 0 || playerBRemainingStones ==0){
            Integer totalA = playerARemainingStones + game.getRightHouseStones();
            Integer totalB = playerBRemainingStones + game.getLeftHouseStones();

            this.leftHouse.setStones(totalB.toString());
            this.rightHouse.setStones(totalA.toString());

            if (totalA > totalB)
                this.winLabel.setText("Game Over!. Player A Won!!!");
            else
                this.winLabel.setText("Game Over!. Player B Won!!!");

            this.winLabel.setVisible(true);
        }
    }
}


MainView Class

MainView is a Vertical Layout which includes all components of the game. It also provides the default routing path for the web application called 'mancala'.


@Route ("mancala")
@Slf4j
public class MainView extends VerticalLayout {

private final Button startGameBtn;

final Label gameIdLabel;
final TextField gameIdTextField;

@Autowired
private GameController gameController;

final KalahaGameComponent kalahaGameComponent;

public MainView(KalahaGameComponent kalahaGameComponent, GameController gameController) {
this.kalahaGameComponent = kalahaGameComponent;
this.gameController = gameController;

// build the game information layout
this.startGameBtn = new Button("Start Game");
this.gameIdLabel = new Label("Game Id:");
this.gameIdTextField = new TextField("", "", "");
this.gameIdTextField.setReadOnly(true);
this.gameIdTextField.setMinLength(50);

// build layout for game id
HorizontalLayout gameIdLayout = new HorizontalLayout(gameIdLabel, gameIdTextField);
gameIdLayout.setAlignItems(Alignment.CENTER);
add(gameIdLayout);

// adding the game itself
add(kalahaGameComponent);

// build layout for actions
HorizontalLayout actions = new HorizontalLayout(startGameBtn);
add(actions);

        // Instantiate and edit new Customer the new button is clicked
        startGameBtn.addClickListener(e -> {
            try {
                KalahaGame game = this.gameController.startNewGame();
                kalahaGameComponent.newGame(game);
                this.gameIdTextField.setValue(game.getId());
                this.kalahaGameComponent.getPlayerTurnTextField().setValue("");

                Notification.show("New Game started. id:" + game.getId(), 3000, Notification.Position.MIDDLE);

            } catch (ApiConnectionException ex) {
                Notification.show("Error!. Message:" + ex.getMessage());
                log.error(ex.getMessage(), ex);
            }
        });
}
}


5 — Exception Handling

We have defined a custom exception class to handle exceptions might throw while interacting with 'mancala-api' service:

public class ApiConnectionException extends RuntimeException {
    public ApiConnectionException(String message) {
        super(message);
    }
}


6 — Custom Events

Using Events is the most appropriate way of handling various situations might happen while users are interacting with our user interface. We have defined a custom Event class called SowEvent representing a single sow event while user clicks on specific pit index:

/*
    This event is fired when user clicks on any pit to sow the game. As a result of this event, a call is made to

    Mancala Api and the application is filled with the results of sowing the pit for selected index
 */

@Getter
@Setter
public class SowEvent extends ApplicationEvent {

    private KalahaGame game;
    private Integer pitIndex;
    public SowEvent(Object source, KalahaGame game, Integer pitIndex) {
        super(source);
        this.game = game;
        this.pitIndex = pitIndex;
    }
}


7 — Spring Sleuth and Zipkin Configuration

To enable Spring Cloud Sleuth functioning for this application, we have to add below property in our application.properties file:

#Sleuth configurations
spring.sleuth.sampler.probability=1


To enable Spring Cloud Zipkin functioning, we have to add below properties in our application.properties file:


#Zipkin configurations
spring.zipkin.base-url=http://localhost:9411/


Since we are using Docker-compose to build and run the Zipkin server, we will override the above property with the actual value in our docker-compose.yml file as below:

 mancala-web:
    build: ../mancala-microservice/mancala-web
    links:
      - consul-server
      - zipkin-server
    environment:
      - SPRING_CLOUD_CONSUL_HOST=consul-server
      - SPRING_APPLICATION_NAME=mancala-web
      - MANCALA_API_SERVICE_ID= mancala-api
      - SPRING_ZIPKIN_BASE_URL=http://zipkin-server:9411/


We also need to have below dependency in our pom.xml file:


<!-- Zipkin -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-zipkin</artifactId>
</dependency>

<!-- Sleuth-->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>


9 — Spring Actuator, Micrometer and Prometheus Server Integration

As I explained in the previous article, Spring Actuator collects metrics about the health of the application at runtime and expose them through well-defined endpoints. We can enhance this feature by adding Micrometer API to our Spring boot application and even further enabling our Spring boot application to send those collected metrics to Prometheus server by adding below dependencies to pom.xml:


<!-- Spring boot actuator to expose metrics endpoint -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

<!-- Micrometer core  -->
<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-core</artifactId>
</dependency>

<!-- Micrometer Prometheus registry  -->
<dependency>
  <groupId>io.micrometer</groupId>
  <artifactId>micrometer-registry-prometheus</artifactId>
</dependency>


10 — ELK-Stack

The same as our 'mancala-api' project, in order to add Elasticsearch integration for our spring boot application, we need to add below dependencies into pom.xml file:

<!-- Dependencies for LogStash -->
<dependency>
  <groupId>ch.qos.logback</groupId>
  <artifactId>logback-core</artifactId>
  <version>1.2.3</version>
</dependency>
<dependency>
  <groupId>net.logstash.logback</groupId>
  <artifactId>logstash-logback-encoder</artifactId>
  <version>6.1</version>
</dependency>


We also need to configure the spring logger to store all logs into specific folder "/logs" through our logback-spring.xml file and then we configure the Filebeat module to ingest logs from that folder and send it to Elasticsearch server where we can search and query about the details through its web interface.

The complete configuration of ELK-Stack servers is provided within the docker-compose.yml file.

11 — Integration Tests

To implement a complete set of integration tests for our two microservices, I have used Wiremock to provide comprehensive testing by mocking our 'mancala-api' API through Wiremock service virtualization facility.

I have also used Spring Cloud Contract to implement Consumer Contract Testing between our two microservices, 'mancala-web' and 'mancala-api'.

12 — Source Code

The complete source code for this project is available in my GitHub repository.

13 — How to Build and Run

To build and run this application, you will need to have Docker-compose installed in your system and follow the instructions I have provided here.

In my next article, I will explain various Testing strategies developed for above Mancala Game implementation: Building Mancala Game in Microservices using Spring Boot Part 4 — Testing.

Your valuable feedback and comments are highly appreciated!


Further Reading

Keys to Success Game Development

 

 

 

 

Top