AWS DynamoDB
Boto3
DynamoDB Client
DynamoDB Resource
DynamoDB Table

When to use dynamodb.client, dynamodb.resource and dynamodb.Table?

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

Boto3 gives you multiple DynamoDB entry points, and each is intended for a different abstraction level. dynamodb.client is low-level and explicit, dynamodb.resource is higher-level and Pythonic, and Table is the focused object for table-centric app code. Choosing the right layer improves readability while preserving access to advanced features when needed.

dynamodb.client: Lowest-Level Control

Use client when you need full API surface and explicit request structure.

python
1import boto3
2
3client = boto3.client("dynamodb", region_name="us-east-1")
4
5resp = client.get_item(
6    TableName="Orders",
7    Key={"order_id": {"S": "A1001"}}
8)
9
10print(resp.get("Item"))

Why choose this layer:

  • new DynamoDB API features appear here first
  • request payload is explicit and predictable
  • easier for advanced middleware and auditing pipelines

Tradeoff is verbosity, especially attribute typing with S, N, M, and L wrappers.

dynamodb.resource: Higher-Level Convenience

Use resource when you want cleaner application code and automatic type handling.

python
1import boto3
2
3resource = boto3.resource("dynamodb", region_name="us-east-1")
4orders = resource.Table("Orders")
5
6resp = orders.get_item(Key={"order_id": "A1001"})
7print(resp.get("Item"))

Benefits:

  • less boilerplate for CRUD operations
  • more readable business logic
  • easier onboarding for teams new to DynamoDB

Tradeoff is less low-level control when you need service-specific edge behaviors.

Table: Daily Driver for One-Table Workflows

Table objects, created from resource, are ideal for app services that interact with one table repeatedly.

python
1import boto3
2from datetime import datetime
3
4resource = boto3.resource("dynamodb", region_name="us-east-1")
5orders = resource.Table("Orders")
6
7orders.put_item(
8    Item={
9        "order_id": "A1002",
10        "status": "PENDING",
11        "created_at": datetime.utcnow().isoformat()
12    }
13)
14
15result = orders.get_item(Key={"order_id": "A1002"})
16print(result.get("Item"))

This layer maps naturally to repository-style application code.

Mixed Approach in Real Systems

Many production systems use both layers:

  • default app CRUD with Table
  • targeted advanced operations with client
python
1import boto3
2
3session = boto3.Session(region_name="us-east-1")
4resource = session.resource("dynamodb")
5client = session.client("dynamodb")
6
7orders = resource.Table("Orders")
8orders.put_item(Item={"order_id": "A1003", "status": "NEW"})
9
10meta = client.describe_table(TableName="Orders")
11print(meta["Table"]["TableStatus"])

This keeps most code readable without losing access to full API depth.

Decision Rules

Use Table or resource when:

  • standard CRUD is primary workload
  • readability and maintainability are priorities
  • you want automatic Python type conversion

Use client when:

  • you need explicit request-level control
  • you use features not exposed conveniently in resource wrappers
  • you need strict parity with DynamoDB API documentation

Document this policy in team standards to avoid inconsistent styles across services.

Testing and Mocking Considerations

Higher-level table code is often easier to mock in unit tests because calls map closely to business operations. For integration tests, using the same abstraction as production code reduces drift. If you must mix layers, keep boundaries explicit in repository utilities.

Serialization and Decimal Handling

One practical reason teams drop to client is strict control over numeric serialization behavior. DynamoDB numeric values and Python decimal handling can create surprises if conversions are implicit in unexpected places. If your domain has strict formatting or audit requirements, centralize serialization in one utility layer and keep abstraction boundaries consistent. This prevents subtle mismatches between write and read paths.

Common Pitfalls

  • Mixing abstraction layers randomly inside one function.
  • Choosing client everywhere and creating unnecessary serialization boilerplate.
  • Assuming resource always exposes newest features immediately.
  • Hiding low-level request details in places where explicit control is required.
  • Failing to define team conventions for which layer is default.

Summary

  • client is low-level and explicit.
  • resource and Table are higher-level and better for most app code.
  • Table is ideal for table-centric CRUD workflows.
  • Use mixed strategy intentionally for advanced feature gaps.
  • Pick one default abstraction and document when to drop lower.

Course illustration
Course illustration

All Rights Reserved.