https://github.com/waelson/go-spanner-repo
A lightweight toolkit for building repository layers on top of Google Cloud Spanner using Go.
https://github.com/waelson/go-spanner-repo
Last synced: 8 months ago
JSON representation
A lightweight toolkit for building repository layers on top of Google Cloud Spanner using Go.
- Host: GitHub
- URL: https://github.com/waelson/go-spanner-repo
- Owner: Waelson
- License: mit
- Created: 2025-09-21T17:31:04.000Z (9 months ago)
- Default Branch: main
- Last Pushed: 2025-09-21T20:18:36.000Z (9 months ago)
- Last Synced: 2025-09-21T20:28:30.891Z (9 months ago)
- Language: Go
- Homepage:
- Size: 62.5 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# Generic Repository for Spanner Database
A lightweight toolkit for building **repository layers** on top of **Google Cloud Spanner** using Go.
It provides generic abstractions for CRUD, transactional operations, key-returning inserts, and pagination.
---
## โจ Features
- Generic typed repository (`repokit`) for domain entities
- Flexible **row mapping** and **mutation building** functions
- Standard CRUD operations:
- `FindByID`, `FindAll`, `FindByIDs`
- `Save`, `Update`, `Delete`
- Key-returning inserts via DML (`SaveReturningKey`) โ works with UUIDs or auto-incremented IDs
- Transaction support (`SaveTx`, `DeleteTx`, `UpdateTx`, `SaveReturningKeyTx`)
- Optional **cursor-based pagination** (no OFFSET required)
---
## ๐ฆ Installation
```bash
go get github.com/Waelson/go-spanner-repo
```
---
## How to user
### Create an entity
```go
type User struct{
UserID string
Email string
}
```
### Implement specialized Repository
```go
type UserRepository struct {
base *repokit.SpannerRepository[User]
}
type UserKey struct {
ID string `spanner:"user_id"`
}
func (u *UserRepository) Save(ctx context.Context, user User) (User, error) {
err := u.base.Save(ctx, user)
return user, err
}
func (u *userNoTxRepository) Delete(ctx context.Context, userID string) error {
key := UserKey{ID: userID}
return u.base.Delete(ctx, key)
}
func (u *userNoTxRepository) Update(ctx context.Context, user User) error {
return u.base.Update(ctx, user)
}
func rowToUser(row *spanner.Row) (User, error) {
var u User
err := row.Columns(&u.UserID, &u.Email)
return u, err
}
func userToMutation(u domain.User) *spanner.Mutation {
return spanner.InsertOrUpdate(
userTable,
[]string{"user_id", "email"},
[]interface{}{u.UserID, u.Email})
}
func NewUserRepository(spannerClient *spanner.Client) UserRepository {
base := repokit.NewBaseRepository[User](
spannerClient,
"tb_users",
[]string{"user_id"},
rowToUser,
userToMutation,
)
return &userTxRepository{base: base}
}
```
### Consuming Repository
```go
func main(){
ctx := context.Background()
// Create Spanner client
spannerClient, err := createSpannerClient(ctx)
if err != nil {
log.Fatal(err)
}
userRepository := repository.UserRepository(spannerClient)
// Create new user
user := domain.User{
UserID: uuid.New().String(), // Generate UUID for user ID
Email: "fake@email.com",
}
// Insert user
user, err = userRepository.Save(ctx, user)
if err != nil {
log.Fatal(err)
}
}
```
## ๐ API Overview
| Method | Description |
| ------------------------------------------ | ---------------------------------------- |
| `FindByID(ctx, key, columns)` | Fetch entity by primary key |
| `FindAll(ctx, columns)` | Fetch all rows |
| `FindByIDs(ctx, keys, columns)` | Lookup multiple entities by key |
| `Save(ctx, entity)` | Insert or update (UPSERT) |
| `Update(ctx, entity)` | Update entity |
| `Delete(ctx, key)` | Delete by primary key |
| `SaveReturningKey(ctx, sql, params, dest)` | Insert with DML and return generated key |
| `SaveTx`, `UpdateTx`, `DeleteTx` | Transactional versions of mutations |
| `SaveReturningKeyTx` | Transactional key-returning insert |
| `Exists(ctx, key)` | Check if entity exists |
---
## โ ๏ธ Notes
- Transactions: Use SaveTx, UpdateTx, DeleteTx inside a ReadWriteTransaction.
- Key returning inserts: Requires DML (INSERT ... THEN RETURN). Works with GENERATE_UUID() or sequence-backed INT64.
- Pagination: Implemented via cursor (pageToken = last seen PK). Avoids OFFSET for performance reasons.
- Composite PKs: Supported; ensure you pass the struct with all primary key fields.
---
## ๐งช Testing
```bash
go test ./repokit -v
```
---
## ๐ค Contributing
Contributions are welcome!
Please open an issue or submit a PR with clear description and tests.