Creating Spring Boot MVC application with AWS DynamoDB in 10 mins

Building a Spring Boot MVC Web App with DynamoDB for a Game Leaderboard

In this article, we’ll walk through the process of building a Spring Boot MVC web application that interacts with an Amazon DynamoDB table to display a game leaderboard. The application will allow users to view top scores for specific games, retrieve scores for individual users, and display all scores for a particular user. We’ll also explore how to use the AWS SDK for Java to interact with DynamoDB.

Prerequisites

Before diving into the Spring Boot project, ensure the following prerequisites are met:

  1. DynamoDB Table Setup: Create a DynamoDB table named leaderboard with the following schema:
    • Partition Key: user_id
    • Sort Key: sk
    • Global Secondary Index (GSI): sk-top_score_index with sk as the partition key and top_score as the sort key.
  2. AWS CLI Configuration: Configure the AWS CLI to connect to your AWS account. This setup is necessary for the application to interact with DynamoDB.
  3. Spring Boot Project Setup: Use Spring Initializr to generate a Spring Boot project with the following dependencies:
    • Spring Web
    • Thymeleaf
    • AWS SDK for DynamoDB (Enhanced Client)

Project Structure

The project follows the MVC (Model-View-Controller) architecture:

  • Model: Leaderboard.java (Plain Java class representing the DynamoDB table structure)
  • View: main.html (Thymeleaf template for displaying the leaderboard)
  • Controller: MyController.java (Handles HTTP requests and interacts with DynamoDB)

Code Walkthrough

1. pom.xml

The pom.xml file includes the necessary dependencies for the project:

<dependencies>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>dynamodb-enhanced</artifactId>
</dependency>
</dependencies>

2. LeaderboardWebApplication.java

This is the entry point of the Spring Boot application. Here, we initialize the DynamoDB client:

@SpringBootApplication
public class LeaderboardWebApplication {

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

@Bean
public DynamoDbClient dynamoDbClient() {
    return DynamoDbClient.builder()
            .region(Region.US_EAST_1) // Specify your AWS region
            .build();
}
}

3. MyController.java

The controller handles three main endpoints:

  1. / (Root Path): Displays the leaderboard for a specific game by querying the GSI.
  2. /getone: Retrieves the top score for a specific user and game.
  3. /playerscore: Retrieves all scores for a specific user.
@Controller
public class MyController {

    @Autowired
    private DynamoDbClient dynamoDbClient;

    @GetMapping("/")
    public String getLeaderboard(@RequestParam(name = "game") String gameParam, Model model)  {

        // Set up mapping of the partition name with the value
        HashMap attrValues =
                new HashMap();

        attrValues.put(":"+"v1", AttributeValue.builder()
                .s(gameParam)
                .build());

        QueryRequest queryReq = QueryRequest.builder()
                .tableName("Leaderboard")
                .keyConditionExpression("sk" + " = :" + "v1")
                .indexName("sk-top_score-index")
                .scanIndexForward(false)
                .expressionAttributeValues(attrValues)
                .build();
        List result= new ArrayList();
        model.addAttribute("playerScore",  result);
        try {
            QueryResponse response = dynamoDbClient.query(queryReq);
            List> itemList  =  response.items();
            //convert to POJO
            for ( Map m: itemList) {
                String userId = m.get("user_id") !=null ? m.get("user_id").s() : "";
                String game =m.get("sk") !=null ?  m.get("sk") .s() :"";
                String userName = m.get("player_name") !=null ? m.get("player_name").s(): "";
                String location = m.get("location") !=null ? m.get("location").s() : "" ;
                Integer topScore = m.get("top_score") !=null ?  Integer.parseInt(m.get("top_score").n()) : null ;
                String scoreDate = m.get("top_score_date") !=null ? m.get("top_score_date").s() : "" ;
                Leaderboard ps = new Leaderboard( userId,  game,  userName, location, topScore,  scoreDate);
                result.add(ps);
            }
            System.out.println(itemList.size());

        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());

        }
        return "main";
    }

    @GetMapping("/getone")
    public String getDynamoDBItem(@RequestParam(name = "userId") String userIdParam,
                                @RequestParam(name = "game") String gameParam, Model model) {

        HashMap keyToGet = new HashMap();

        keyToGet.put("user_id", AttributeValue.builder()
                .s(userIdParam).build());

        keyToGet.put("sk", AttributeValue.builder()
                .s(gameParam).build());

        GetItemRequest request = GetItemRequest.builder()
                .key(keyToGet)
                .tableName("Leaderboard")
                .build();
        List result= new ArrayList();
        try {
            Map m = dynamoDbClient.getItem(request).item();

            if (m != null) {
                String userId = m.get("user_id") !=null ? m.get("user_id").s() : "";
                String game =m.get("sk") !=null ?  m.get("sk") .s() :"";
                String userName = m.get("player_name") !=null ? m.get("player_name").s(): "";
                String location = m.get("location") !=null ? m.get("location").s() : "" ;
                Integer topScore = m.get("top_score") !=null ?  Integer.parseInt(m.get("top_score").n()) : null ;
                String scoreDate = m.get("top_score_date") !=null ? m.get("top_score_date").s() : "" ;
                Leaderboard ps = new Leaderboard( userId,  game,  userName, location, topScore,  scoreDate);
                result.add(ps);
            } else {
                System.out.format("No item found with the key");
            }
            model.addAttribute("playerScore",  result);
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
        }
        return "main";
    }

    @GetMapping("/playerscore")
    public String queryPlayerScore(@RequestParam(name = "userId") String userIdParam, Model model) {

        // Set up mapping of the partition name with the value
        HashMap attrValues =
                new HashMap();

        attrValues.put(":"+"v1", AttributeValue.builder()
                .s(userIdParam)
                .build());

        QueryRequest queryReq = QueryRequest.builder()
                .tableName("Leaderboard")
                .keyConditionExpression("user_id" + " = :" + "v1")
                .expressionAttributeValues(attrValues)
                .build();
        List result= new ArrayList();
        try {
            QueryResponse response = dynamoDbClient.query(queryReq);
            List> itemList  =  response.items();
            //convert to POJO
            for ( Map m: itemList) {
                String userId = m.get("user_id") !=null ? m.get("user_id").s() : "";
                String game =m.get("sk") !=null ?  m.get("sk") .s() :"";
                String userName = m.get("player_name") !=null ? m.get("player_name").s(): "";
                String location = m.get("location") !=null ? m.get("location").s() : "" ;
                Integer topScore = m.get("top_score") !=null ?  Integer.parseInt(m.get("top_score").n()) : null ;
                String scoreDate = m.get("top_score_date") !=null ? m.get("top_score_date").s() : "" ;
                Leaderboard ps = new Leaderboard( userId,  game,  userName, location, topScore,  scoreDate);
                result.add(ps);
            }
            System.out.println(itemList.size());
            //System.out.println(itemList);
            model.addAttribute("playerScore",  result);
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
        }
        return "main";
    }

}

4. main.html

The Thymeleaf template displays the leaderboard data:


<html xmlns:th="https://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>My Leaderboard</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>
<body>
    Game Leaderboard
    <br/>
    <table class="table table-bordered">
        <tr th:each="player : ${playerScore}">
            <td th:text="${player.userId}">User Id</td>
            <td th:text="${player.game}">Game</td>
            <td th:text="${player.topScore}">Top Score</td>
            <td th:text="${player.scoreDate}">Top Score Date</td>
        </tr>
    </table>
</body>
</html>

5. Leaderboard.java

The model class represents the structure of the DynamoDB table:

public class Leaderboard {
    private String userId;
    private String game;
    private String userName;
    private String location;
    private Integer topScore;
    private String scoreDate;

    public Leaderboard(String userId, String game, String userName, String location, Integer topScore, String scoreDate) {
        this.userId = userId;
        this.game = game;
        this.userName = userName;
        this.location = location;
        this.topScore = topScore;
        this.scoreDate = scoreDate;
    }
    // Getters and Setters
}

Running the Application

  1. Test and Start the Spring Boot application locally using the command:
    mvn spring-boot:run

Conclusion

In this article, we’ve built a Spring Boot MVC web application that interacts with DynamoDB to display a game leaderboard. We’ve covered how to set up the DynamoDB table, configure the Spring Boot project, and implement the MVC components. The application demonstrates how to query DynamoDB using the AWS SDK for Java and display the results using Thymeleaf.

You can find the complete source code on GitHub. Happy coding!



Video explain the table design:
https://youtu.be/V0GtrBfY7XM

Prerequisite: Install the AWS CLI:
https://youtu.be/pE-Q_4YXlR0

Video explain the how to create the table:
https://youtu.be/sBZIVLlmpxY

Popular posts from this blog

Sample Apps: Spring data MongoDB and JSF Integration tutorial (PART 1)

Customizing Spring Data JPA Repository

Adding Hibernate Entity Level Filtering feature to Spring Data JPA Repository