Table Creation

This tenant-per-table partitioning model uses the context of the current tenant identifier (passed in the JWT token) to generate our table name. Let’s look at how this is resolved in the code of our application service. Open the Cloud9 IDE in the AWS console and select the “Serverless SaaS Workshop IDE“. With the IDE, we can now examine the code that currently exists for our Order service. To get there, open the “Lab 3“ folder in the left-hand pane of the page. Under the “order-service/src/main/java“ you will find the various Java files that makeup our Order service. Double-click on the “OrderServiceDAL.java“ file and look at the insertOrder() function here. The code will appear as follows:

public Order insertOrder(Map<String, Object> event, Order order) {
    UUID orderId = UUID.randomUUID();
    LOGGER.info("OrderServiceDAL::insertOrder " + orderId);

    order.setId(orderId);
    try{
        Map<String, AttributeValue> item = DynamoDbHelper.toAttributeValueMap(order);
        PutItemResponse response = ddb.putItem(request -> request.tableName(tableName(event)).item(item));
    } catch (DynamoDbException e) {
        LOGGER.error("OrderServiceDAL::insertOrder " + getFullStackTrace(e));
        throw new RuntimeException(e);
    }

    return order;
}

Within this function, you’ll see line 100 that makes a ddb.putItem() call that is calling a helper class, DynamoDBHelper (ddb) to put an item to a table. As part of this call, the code call getTableName(), supplying the context of the request in the “event“ parameter. The parameter contains our JWT token with our tenant context. They key here is the getTableName() call. Let’s look at this implementation of tableName() in line 126 to see how it generates a table name. The code is as follows:

private String tableName(Map<String, Object> event) {
    String tenantId = new TokenManager().getTenantId(event);

    String tableName = "order_fulfillment_" + tenantId;
    if (!tenantTableCache.containsKey(tenantId) || !tenantTableCache.get(tenantId).equals(tableName)) {
        boolean exits = false;
        ListTablesResponse response = ddb.listTables();
        for (String table : response.tableNames()) {
            if (table.equals(tableName)) {
                exits = true;
                break;
            }
        }
        if (!exits) {
            CreateTableResponse createTable = ddb.createTable(request -> request
                    .tableName(tableName)
                    .attributeDefinitions(AttributeDefinition.builder().attributeName("id").attributeType(ScalarAttributeType.S).build())
                    .keySchema(KeySchemaElement.builder().attributeName("id").keyType(KeyType.HASH).build())
                    .provisionedThroughput(ProvisionedThroughput.builder().readCapacityUnits(5L).writeCapacityUnits(5L).build())
            );
        }
        tenantTableCache.put(tenantId, tableName);
    }

    return tenantTableCache.get(tenantId);
}

You’ll notice that this code first makes a call to the TokenManager to get our current tenant identifier from the supplied JWT token. It then creates and table name that is the concatenation of “order_fulfillment_“ and the tenant id we retrieved. This ensures that each table name is unique for each tenant.