← Back to Home

Module 3: Writing to DynamoDB + HTTP

Module Overview

Learn how to write data to DynamoDB and understand the fundamentals of HTTP for building RESTful services.

Learning Objectives

DynamoDB Saving

Writing Data to DynamoDB

The DynamoDB Enhanced Client provides several operations for creating and updating items in DynamoDB tables:

Creating New Items

To add a new item to a DynamoDB table:

// Create a new MusicItem instance
MusicItem newSong = new MusicItem();
newSong.setArtist("Imagine Dragons");
newSong.setSongTitle("Radioactive");
newSong.setAlbumTitle("Night Visions");
newSong.setYearReleased(2012);
newSong.setGenres(new HashSet<>(Arrays.asList("Alternative Rock", "Indie Rock")));

// Save the item to DynamoDB
musicTable.putItem(newSong);
System.out.println("Added new song: " + newSong.getSongTitle() + " by " + newSong.getArtist());

The putItem method will overwrite any existing item with the same key, so be careful when using it on items that might already exist.

Updating Existing Items

There are two main approaches to updating items:

  1. Complete Replacement - Using putItem to replace the entire item
  2. Partial Update - Using updateItem to modify specific attributes

For partial updates, you can use the UpdateItemEnhancedRequest:

// Create a key for the item to update
Key key = Key.builder()
    .partitionValue("Imagine Dragons")
    .sortValue("Radioactive")
    .build();

// Define the update expression
UpdateItemEnhancedRequest<MusicItem> updateRequest = UpdateItemEnhancedRequest
    .builder(MusicItem.class)
    .key(key)
    .updateExpression(Expression.builder()
        .expression("SET yearReleased = :year, genres = :genres")
        .expressionValues(singletonMap(":year", AttributeValue.builder().n("2013").build()))
        .expressionValues(singletonMap(":genres", AttributeValue.builder()
            .ss(Arrays.asList("Alternative Rock", "Electronic Rock"))
            .build()))
        .build())
    .build();

// Execute the update
musicTable.updateItem(updateRequest);

Conditional Writes

DynamoDB supports conditional writes to ensure that operations only proceed if certain conditions are met:

// Create a new song, but only if it doesn't already exist
PutItemEnhancedRequest<MusicItem> putRequest = PutItemEnhancedRequest
    .builder(MusicItem.class)
    .item(newSong)
    .conditionExpression(Expression.builder()
        .expression("attribute_not_exists(artist) AND attribute_not_exists(songTitle)")
        .build())
    .build();

try {
    musicTable.putItem(putRequest);
    System.out.println("Item added successfully");
} catch (ConditionalCheckFailedException e) {
    System.out.println("Song already exists. Cannot overwrite.");
}

Atomic Counters

For numeric attributes that need to be incremented or decremented:

// Increment the play count for a song
UpdateItemEnhancedRequest<MusicItem> updateCounter = UpdateItemEnhancedRequest
    .builder(MusicItem.class)
    .key(key)
    .updateExpression(Expression.builder()
        .expression("SET playCount = if_not_exists(playCount, :zero) + :increment")
        .expressionValues(Map.of(
            ":zero", AttributeValue.builder().n("0").build(),
            ":increment", AttributeValue.builder().n("1").build()))
        .build())
    .build();

musicTable.updateItem(updateCounter);

These write operations form the basis of CRUD functionality with DynamoDB, enabling your applications to create and modify data as needed.

Introduction to RESTful APIs

HTTP and RESTful API Fundamentals

HTTP (Hypertext Transfer Protocol) is the foundation of data communication on the web, and RESTful APIs are built on top of HTTP to provide standardized interfaces for systems to communicate.

Key HTTP Concepts

  • IP Address - Numerical designation assigned to each device connected to a network (e.g., 192.168.1.1)
  • Domain Name Server (DNS) - System that translates human-readable domain names to IP addresses
  • Ports - Endpoints for communication within a host (e.g., 80 for HTTP, 443 for HTTPS)
  • URL (Uniform Resource Locator) - Complete address to a resource on the web

A URL consists of several components:

https://api.example.com:443/resources/123?filter=active
| | | | |
| | | | └── Query parameters
| | | └── Resource path/endpoint
| | └── Port
| └── Domain name (FQDN)
└── Protocol

RESTful APIs

REST (Representational State Transfer) is an architectural style for designing networked applications. RESTful APIs use HTTP methods explicitly and follow a stateless client-server model.

A RESTful API is an interface that allows different systems to communicate using HTTP methods to perform operations on resources. Key characteristics include:

  • Statelessness - Each request contains all information needed to process it
  • Resource-based - Resources are identified by URLs
  • Standard HTTP methods - Used for operations (GET, POST, PUT, DELETE)
  • Representation - Resources can be represented in different formats (JSON, XML)

HTTP Methods and Database Operations

There's a natural mapping between HTTP methods and database CRUD operations:

HTTP Method Database Operation Description
GET Read Retrieve resources
POST Create Create new resources
PUT Update Update existing resources
DELETE Delete Remove resources

This mapping helps design intuitive APIs that follow web standards and are easy to understand for developers.

Writing to DynamoDB + HTTP Overview

Combining DynamoDB and HTTP

Building web services often involves connecting RESTful APIs with database operations. Here's how you might structure a Java application that uses both DynamoDB and HTTP:

Architecture Overview

A typical web service architecture includes:

  1. API Layer - Handles HTTP requests and responses
  2. Service Layer - Contains business logic
  3. Data Access Layer - Interfaces with DynamoDB

The flow of data typically follows this pattern:

HTTP Request → API Layer → Service Layer → Data Access Layer → DynamoDB
HTTP Response ← API Layer ← Service Layer ← Data Access Layer ← DynamoDB

Example: RESTful API with DynamoDB

Here's a conceptual example of how a music library API might be structured:

// API Layer (Controller)
@RestController
@RequestMapping("/api/songs")
public class SongController {
    private final SongService songService;

    @Autowired
    public SongController(SongService songService) {
        this.songService = songService;
    }

    @GetMapping("/{artist}/{title}")
    public ResponseEntity<SongResponse> getSong(@PathVariable String artist,
            @PathVariable String title) {
        MusicItem song = songService.getSong(artist, title);
        return ResponseEntity.ok(convertToResponse(song));
    }

    @PostMapping
    public ResponseEntity<SongResponse> createSong(@RequestBody SongRequest request) {
        MusicItem song = songService.createSong(convertToModel(request));
        return ResponseEntity.status(HttpStatus.CREATED).body(convertToResponse(song));
    }

    // Other endpoints: PUT, DELETE, etc.
}

// Service Layer
@Service
public class SongService {
    private final SongRepository songRepository;

    @Autowired
    public SongService(SongRepository songRepository) {
        this.songRepository = songRepository;
    }

    public MusicItem getSong(String artist, String title) {
        return songRepository.findByArtistAndTitle(artist, title)
            .orElseThrow(() -> new ResourceNotFoundException("Song not found"));
    }

    public MusicItem createSong(MusicItem song) {
        // Validation logic
        return songRepository.save(song);
    }

    // Other service methods
}

// Data Access Layer
@Repository
public class SongRepository {
    private final DynamoDbTable<MusicItem> musicTable;

    public SongRepository(DynamoDbEnhancedClient enhancedClient) {
        this.musicTable = enhancedClient.table("Music", TableSchema.fromBean(MusicItem.class));
    }

    public Optional<MusicItem> findByArtistAndTitle(String artist, String title) {
        Key key = Key.builder()
            .partitionValue(artist)
            .sortValue(title)
            .build();

        MusicItem item = musicTable.getItem(key);
        return Optional.ofNullable(item);
    }

    public MusicItem save(MusicItem song) {
        musicTable.putItem(song);
        return song;
    }

    // Other repository methods
}

This layered approach separates concerns and makes your code more maintainable and testable. The API layer handles HTTP-specific concerns, the service layer contains business logic, and the repository layer deals with DynamoDB operations.

Resources