All Projects → cashapp → Tempest

cashapp / Tempest

Licence: apache-2.0
Typesafe DynamoDB for Kotlin and Java.

Programming Languages

java
68154 projects - #9 most used programming language
kotlin
9241 projects

Projects that are alternatives of or similar to Tempest

Laravel Dynamodb
Eloquent syntax for DynamoDB
Stars: ✭ 342 (+968.75%)
Mutual labels:  aws, dynamodb
Dazn Lambda Powertools
Powertools (logger, HTTP client, AWS clients, middlewares, patterns) for Lambda functions.
Stars: ✭ 501 (+1465.63%)
Mutual labels:  aws, dynamodb
Aws Mobile Appsync Chat Starter Angular
GraphQL starter progressive web application (PWA) with Realtime and Offline functionality using AWS AppSync
Stars: ✭ 449 (+1303.13%)
Mutual labels:  aws, dynamodb
Dynamodb Documentclient Cheat Sheet
DynamoDB JavaScript DocumentClient cheat sheet
Stars: ✭ 293 (+815.63%)
Mutual labels:  aws, dynamodb
Dynamodb Toolbox
A simple set of tools for working with Amazon DynamoDB and the DocumentClient
Stars: ✭ 752 (+2250%)
Mutual labels:  aws, dynamodb
Lambdaguard
AWS Serverless Security
Stars: ✭ 300 (+837.5%)
Mutual labels:  aws, dynamodb
Workshop Donkeytracker
Workshop to build a serverless tracking application for your mobile device with an AWS backend
Stars: ✭ 27 (-15.62%)
Mutual labels:  aws, dynamodb
Terraform Aws Tfstate Backend
Terraform module that provision an S3 bucket to store the `terraform.tfstate` file and a DynamoDB table to lock the state file to prevent concurrent modifications and state corruption.
Stars: ✭ 229 (+615.63%)
Mutual labels:  aws, dynamodb
Aws Mobile React Sample
A React Starter App that displays how web developers can integrate their front end with AWS on the backend. The App interacts with AWS Cognito, API Gateway, Lambda and DynamoDB on the backend.
Stars: ✭ 650 (+1931.25%)
Mutual labels:  aws, dynamodb
Dynamodb Gui Client
DynamoDb GUI Client
Stars: ✭ 540 (+1587.5%)
Mutual labels:  aws, dynamodb
Arc.codes
The Architect web site! 🌩
Stars: ✭ 271 (+746.88%)
Mutual labels:  aws, dynamodb
Jcabi Dynamodb Maven Plugin
DynamoDB Local Maven Plugin
Stars: ✭ 26 (-18.75%)
Mutual labels:  aws, dynamodb
Aws Toolkit Eclipse
AWS Toolkit for Eclipse – an open-source plugin for developing, deploying, and managing AWS applications.
Stars: ✭ 252 (+687.5%)
Mutual labels:  aws, dynamodb
Dynamon
😈 Dynamon is GUI client for DynamoDB, can connect local dynamodb.
Stars: ✭ 324 (+912.5%)
Mutual labels:  aws, dynamodb
Komiser
☁️ Cloud Environment Inspector 👮🔒 💰
Stars: ✭ 2,684 (+8287.5%)
Mutual labels:  aws, dynamodb
Dynogels
DynamoDB data mapper for node.js. Originally forked from https://github.com/ryanfitz/vogels
Stars: ✭ 471 (+1371.88%)
Mutual labels:  aws, dynamodb
Dialetus Service
API to Informal dictionary for the idiomatic expressions that each Brazilian region It has
Stars: ✭ 202 (+531.25%)
Mutual labels:  aws, dynamodb
Serverless Analytics
Track website visitors with Serverless Analytics using Kinesis, Lambda, and TypeScript.
Stars: ✭ 219 (+584.38%)
Mutual labels:  aws, dynamodb
Serverless Dynamodb Local
Serverless Dynamodb Local Plugin - Allows to run dynamodb locally for serverless
Stars: ✭ 530 (+1556.25%)
Mutual labels:  aws, dynamodb
Dynamodb Admin
GUI for DynamoDB Local or dynalite
Stars: ✭ 803 (+2409.38%)
Mutual labels:  aws, dynamodb

Tempest

Typesafe DynamoDB for Kotlin and Java.

See the project website for documentation and APIs.

Efficient DynamoDB

DynamoDB applications perform best (and cost the least to operate!) when data is organized for locality:

  • Multiple types per table: The application can store different entity types in a single table. DynamoDB schemas are flexible.
  • Related entities are stored together: Entities that are accessed together should be stored together. This makes it possible to answer common queries in as few requests as possible, ideally one.

Example

Let's build a music library with the following features:

  • Fetching multiple albums, each of which contains multiple tracks.
  • Fetching individual tracks.

We express it like this in code:

=== "Kotlin"

```kotlin
interface MusicLibrary {
  fun getAlbum(key: AlbumKey): Album
  fun getTrack(key: TrackKey): Track
}

data class Album(
  val album_title: String,
  val album_artist: String,
  val release_date: String,
  val genre: String,
  val tracks: List<Track>
)

data class Track(
  val track_title: String,
  val run_length: String
)
```

=== "Java"

```java
public interface MusicLibrary {
  Album getAlbum(AlbumKey key);
  Track getTrack(TrackKey key); 
}

public class Album {
  public final String album_title;
  public final String album_artist;
  public final String release_date;
  public final String genre;
  public final List<Track> tracks; 
}

public class Track(
  public final String track_title;
  public final String run_length;
)
```

We optimize for this access pattern by putting albums and tracks in the same table:

Primary Key Attributes
partition_key sort_key
ALBUM_1 INFO album_title album_artiest release_date genre
The Dark Side of the Moon Pink Floyd 1973-03-01 Progressive rock
ALBUM_1 TRACK_1 track_title run_length    
Speak to Me PT1M13S    
ALBUM_1 TRACK_2 track_title run_length    
Breathe PT2M43S    
ALBUM_1 TRACK_3 track_title run_length    
On the Run PT3M36S    
...
ALBUM_2 INFO album_title album_artiest release_date genre
The Wall Pink Floyd 1979-11-30 Progressive rock
ALBUM_2 TRACK_1 track_title run_length    
In the Flesh? PT3M20S    
...

This table uses a composite primary key, (parition_key, sort_key), to identify each item.

  • The key ("ALBUM_1", "INFO") identifies ALBUM_1's metadata.
  • The key ("ALBUM_1", "TRACK_1") identifies ALBUM_1's first track.

This table stores tracks belonging to the same album together and sorts them by the track number. The application needs only one request to DynamoDB to get the album and its tracks.

aws dynamodb query \
    --table-name music_library_items \
    --key-conditions '{ 
        "PK": { 
            "ComparisonOperator": "EQ",
            "AttributeValueList": [ { "S": "ALBUM_1" } ]
        } 
    }'

Why Tempest?

For locality, we smashed together several entity types in the same table. This improves performance! But it breaks type safety in DynamoDBMapper.

DynamoDBMapper API

DynamoDBMapper, the official Java API, forces you to write weakly-typed code that models the actual persistence type.

=== "Kotlin"

```kotlin
// NOTE: This is not Tempest! It is an example used for comparison.
@DynamoDBTable(tableName = "music_library_items")
class MusicLibraryItem {
  // All Items.
  @DynamoDBHashKey
  var partition_key: String? = null
  @DynamoDBRangeKey
  var sort_key: String? = null

  // AlbumInfo.
  @DynamoDBAttribute
  var album_title: String? = null
  @DynamoDBAttribute
  var album_artist: String? = null
  @DynamoDBAttribute
  var release_date: String? = null
  @DynamoDBAttribute
  var genre: String? = null

  // AlbumTrack.
  @DynamoDBAttribute
  var track_title: String? = null
  @DynamoDBAttribute
  var run_length: String? = null
}
```

=== "Java"

```java
// NOTE: This is not Tempest! It is an example used for comparison.
@DynamoDBTable(tableName = "music_library_items")
public class MusicLibraryItem {
  // All Items.
  @DynamoDBHashKey
  public String partition_key;
  @DynamoDBRangeKey
  public String sort_key;

  // AlbumInfo.
  @DynamoDBAttribute
  public String album_title;
  @DynamoDBAttribute
  public String album_artist;
  @DynamoDBAttribute
  public String release_date;
  @DynamoDBAttribute
  public String genre;

  // AlbumTrack.
  @DynamoDBAttribute
  public String track_title;
  @DynamoDBAttribute
  public String run_length;
}
```

Note that MusicLibraryItem is a union type of all the entity types: AlbumInfo and AlbumTrack. Because all of its attributes are nullable and mutable, code that interacts with it is brittle and error prone.

Tempest API

Tempest restores maintainability without losing locality. It lets you declare strongly-typed key and item classes for each logical type in the domain layer.

=== "Kotlin"

```kotlin
data class AlbumInfo(
  @Attribute(name = "partition_key")
  val album_token: String,
  val album_title: String,
  val album_artist: String,
  val release_date: String,
  val genre_name: String
) {
  @Attribute(prefix = "INFO_")
  val sort_key: String = ""

  data class Key(
    val album_token: String
  ) {
    val sort_key: String = ""
  }
}

data class AlbumTrack(
  @Attribute(name = "partition_key")
  val album_token: String,
  @Attribute(name = "sort_key", prefix = "TRACK_")
  val track_token: String,
  val track_title: String,
  val run_length: String
) {
  data class Key(
    val album_token: String,
    val track_token: String
  )
}
```

=== "Java"

```java
public class AlbumInfo {
  @Attribute(name = "partition_key")
  public final String album_token;
  public final String album_title;
  public final String artist_name;
  public final String release_date;
  public final String genre_name;

  @Attribute(prefix = "INFO_")
  public final String sort_key = "";

  public static class Key {
    public final String album_token;
    public final String sort_key = "";
  }
}

public class AlbumTrack {
  @Attribute(name = "partition_key")
  public final String album_token;
  @Attribute(name = "sort_key", prefix = "TRACK_")
  public final String track_token;
  public final String track_title;
  public final String run_length;

  public static class Key {
    public final String album_token;
    public final String track_token;
  }
}
```

You build business logic with logical types. Tempest handles mapping them to the underlying persistence type.

=== "Kotlin"

```kotlin
interface MusicLibraryTable : LogicalTable<MusicLibraryItem> {
  val albumInfo: InlineView<AlbumInfo.Key, AlbumInfo>
  val albumTracks: InlineView<AlbumTrack.Key, AlbumTrack>
}

private val musicLibrary: MusicLibraryTable

// Load.
fun getAlbumTitle(albumToken: String): String? {
  val key = AlbumInfo.Key(albumToken)
  val albumInfo = musicLibrary.albumInfo.load(key) ?: return null
  return albumInfo.album_title
}

// Update.
fun addAlbumTrack(
  albumToken: String, 
  track_token: String, 
  track_title: String, 
  run_length: String
) {
  val newAlbumTrack = AlbumTrack(albumToken, track_token, track_title, run_length)
  musicLibrary.albumTracks.save(newAlbumTrack)
} 

// Query.
fun getAlbumTrackTitles(albumToken: String): List<String> {
  val page = musicLibrary.albumTracks.query(
    keyCondition = BeginsWith(AlbumTrack.Key(albumToken))
  )
  return page.contents.map { it.track_title }
}
```

=== "Java"

```java
public interface MusicLibraryTable extends LogicalTable<MusicLibraryItem> {
  InlineView<AlbumInfo.Key, AlbumInfo> albumInfo();
  InlineView<AlbumTrack.Key, AlbumTrack> albumTracks();
}

private MusicLibraryTable musicLibrary; 

// Load.
@Nullable
public String getAlbumTitle(String albumToken) {
  AlbumInfo albumInfo = table.albumInfo().load(new AlbumInfo.Key(albumToken));
  if (albumInfo == null) {
    return null;
  }
  return albumInfo.album_title;
}

// Update.
public void addAlbumTrack(
  String albumToken, 
  String track_token, 
  String track_title, 
  String run_length
) {
  AlbumTrack newAlbumTrack = new AlbumTrack(albumToken, track_token, track_title, run_length);
  musicLibrary.albumTracks().save(newAlbumTrack);
}

// Query.
public List<String> getAlbumTrackTitles(String albumToken) {
  Page<AlbumTrack.Key, AlbumTrack> page = musicLibrary.albumTracks().query(
      // keyCondition.
      new BeginsWith<>(
          // prefix.
          new AlbumTrack.Key(albumToken)
      )
  );
  return page.getContents().stream().map(track -> track.track_title).collect(Collectors.toList());
}
```

Get Tempest

For AWS SDK 1.x:

implementation "app.cash.tempest:tempest:1.4.0"

For AWS SDK 2.x:

implementation "app.cash.tempest:tempest2:1.4.0"

License

Copyright 2020 Square, Inc.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

   http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].