Repository Pattern¶
Repositories provide the primary interface for interacting with DynamoDB tables. They offer CRUD operations, query building, and automatic table name resolution.
Defining Repositories¶
Repositories embed dynorm.Repository[T] where T is your entity type:
// Table name auto-derived: User → users
type UserRepository struct {
dynorm.Repository[User]
}
var UserRepo = &UserRepository{}
// With explicit table name
type StaffRepository struct {
dynorm.Repository[User] `table:"staff"`
}
var StaffRepo = &StaffRepository{}
Table Name Resolution¶
Table names are automatically resolved:
- Type name → plural snake_case:
User→users,CreditCard→credit_cards - Table tag override:
table:"custom_name"overrides auto-derivation - Prefix from environment:
DYNORM_TABLE_PREFIXprepended to all names
// With DYNORM_TABLE_PREFIX=prod-
// User entity → "prod-users"
// CreditCard entity → "prod-credit_cards"
CRUD Operations¶
Find¶
Retrieves an entity by its primary key (ULID ID):
user, err := UserRepo.Find("01HQ3K4N5M6P7Q8R9S0T1U2V3W")
if err != nil {
return err
}
if user == nil {
// Not found
}
Save¶
Creates or updates an entity. For new entities, a ULID is auto-generated:
// Create new user
user := &User{
Email: "john@example.com",
FirstName: "John",
LastName: "Doe",
}
err := UserRepo.Save(user)
// user.ID is now set: "01HQ3K..."
// user.CreatedAt derived from ULID timestamp
// user.UpdatedAt set to current time
// Update existing user
user.Status = "verified"
err = UserRepo.Save(user)
// user.UpdatedAt updated
Delete¶
Removes an entity from the database:
// Delete by entity
err := UserRepo.Delete(user)
// Delete by ID
err := UserRepo.DeleteByID("01HQ3K4N5M6P7Q8R9S0T1U2V3W")
All¶
Returns all entities (performs a table scan):
Performance
All() performs a full table scan. Use queries with conditions for large tables.
Count¶
Returns the total count of entities:
Query Building¶
Repositories provide fluent query building methods:
// Start a query with Where
users, err := UserRepo.
Where("Status", "=", "active").
GetAll()
// Start with Select
users, err := UserRepo.
Select("ID", "Email", "FirstName").
Where("Status", "=", "active").
GetAll()
// Start with OrderBy
users, err := UserRepo.
OrderBy("CreatedAt", "desc").
Limit(10).
GetAll()
// Start with Limit
users, err := UserRepo.
Limit(100).
GetAll()
See Query Builder for complete query documentation.
Soft Deletes¶
Repositories support soft delete operations for entities with soft_deletes:"true":
type Post struct {
dynorm.Entity `soft_deletes:"true"`
Title string
Content string
}
type PostRepository struct {
dynorm.Repository[Post]
}
var PostRepo = &PostRepository{}
Soft Delete¶
Marks an entity as deleted without removing it:
Restore¶
Removes the soft delete marker:
Architecture¶
graph TB
subgraph "Repository Pattern"
Repo[Repository<T>]
Entity[Entity]
Query[Query Builder]
Schema[Schema Parser]
end
subgraph "Operations"
Find[Find by ID]
Save[Save]
Delete[Delete]
All[All]
Count[Count]
end
subgraph "AWS"
DDB[(DynamoDB)]
end
Repo --> Entity
Repo --> Query
Repo --> Schema
Repo --> Find
Repo --> Save
Repo --> Delete
Repo --> All
Repo --> Count
Find --> DDB
Save --> DDB
Delete --> DDB
All --> DDB
Count --> DDB Lazy Initialization¶
Repositories initialize lazily on first use:
- Schema is parsed from struct tags
- Table name is resolved (type name + prefix + tag override)
- DynamoDB client is initialized (via
sync.Once)
// No initialization happens here
var UserRepo = &UserRepository{}
// First use triggers initialization
user, err := UserRepo.Find("01HQ3K...")
Batch Operations¶
Repositories support batch operations for efficiency:
// Batch get
users, err := UserRepo.BatchGet([]string{"id1", "id2", "id3"})
// Batch save
err := UserRepo.BatchSave(users)
// Batch delete
err := UserRepo.BatchDelete([]string{"id1", "id2", "id3"})
See Batch Operations for details.
Best Practices¶
Do
- Define repositories as global variables
- Use meaningful repository names (
UserRepository,OrderRepository) - Leverage auto-table names (only override when necessary)
- Use soft deletes for audit trails
- Consider GSIs early for common query patterns
Don't
- Create multiple repository instances for the same entity
- Bypass the repository to access DynamoDB directly
- Forget to handle errors
Complete Example¶
package main
import (
"fmt"
"log"
"github.com/go-gamma/dynorm"
)
// Entity
type Product struct {
dynorm.Entity
Name string `dynorm:"gsi:ByName"`
Category string `dynorm:"gsi:ByCategory"`
Price float64
InStock bool
Description string
}
// Repository
type ProductRepository struct {
dynorm.Repository[Product]
}
var ProductRepo = &ProductRepository{}
func main() {
// Create
product := &Product{
Name: "Laptop",
Category: "Electronics",
Price: 999.99,
InStock: true,
Description: "High-performance laptop",
}
if err := ProductRepo.Save(product); err != nil {
log.Fatal(err)
}
fmt.Printf("Created product: %s\n", product.ID)
// Read
found, err := ProductRepo.Find(product.ID)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found: %s - $%.2f\n", found.Name, found.Price)
// Update
product.Price = 899.99
if err := ProductRepo.Save(product); err != nil {
log.Fatal(err)
}
// Query
electronics, err := ProductRepo.
Where("Category", "=", "Electronics").
Where("InStock", "=", true).
OrderBy("Price", "asc").
GetAll()
if err != nil {
log.Fatal(err)
}
fmt.Printf("Found %d electronics in stock\n", electronics.Count())
// Delete
if err := ProductRepo.Delete(product); err != nil {
log.Fatal(err)
}
}
Next Steps¶
- Collection - Work with query results
- Query Builder - Build complex queries
- Batch Operations - Bulk operations