dal defines a common Go storage interface for CRUD, list, count, and bulk-create operations. Twelve adapters implement that interface for embedded, filesystem, database, cache, search, and cloud backends; callers can use a concrete adapter or program against storage.IStorage. The package also provides concurrent single- and multi-storage helpers, operation hooks, counters, logging, and Elastic APM instrumentation.
go get github.com/thalesfsp/dal/v3/storageThe module path in go.mod is github.com/thalesfsp/dal/v3.
| Package | Backend and use |
|---|---|
bolt |
bbolt: an embedded, persistent key/value database in one file. |
dynamodb |
AWS DynamoDB tables. |
elasticsearch |
Elasticsearch indices. |
file |
The local filesystem, with one file per document. |
memory |
A volatile, in-process sync.Map. |
mongodb |
MongoDB databases and collections. |
mysql |
MySQL tables through database/sql and sqlx. |
postgres |
PostgreSQL tables through database/sql and sqlx. |
redis |
Redis keys and values. |
s3 |
Objects in an AWS S3 bucket. |
sftp |
Files on a remote SFTP server. |
sqlite |
SQLite tables through database/sql and sqlx. |
This example uses the in-process memory adapter and performs Create → Retrieve → List → Count → Update → Delete. It follows the runnable examples in memory/example_test.go and needs no external service.
package main
import (
"context"
"fmt"
"github.com/thalesfsp/dal/v3/memory"
"github.com/thalesfsp/params/v2/count"
"github.com/thalesfsp/params/v2/create"
"github.com/thalesfsp/params/v2/customsort"
"github.com/thalesfsp/params/v2/delete"
"github.com/thalesfsp/params/v2/list"
"github.com/thalesfsp/params/v2/retrieve"
"github.com/thalesfsp/params/v2/update"
)
type book struct {
ID string `json:"id"`
Title string `json:"title"`
}
func must(err error) {
if err != nil {
panic(err)
}
}
func main() {
ctx := context.Background()
strg, err := memory.New(ctx)
must(err)
defer func() { must(strg.Close()) }()
id, err := strg.Create(ctx, "book-dune", "", &book{
ID: "book-dune", Title: "Dune",
}, &create.Create{})
must(err)
fmt.Println("created", id)
var got book
must(strg.Retrieve(ctx, id, "", &got, &retrieve.Retrieve{}))
fmt.Println("retrieved", got.Title)
var books memory.ResponseList[book]
must(strg.List(ctx, "", &books, &list.List{
Search: "book-*",
Sort: customsort.SortSlice{{"id", customsort.Asc}},
}))
fmt.Println("listed", len(books.Items))
n, err := strg.Count(ctx, "", &count.Count{Search: "book-*"})
must(err)
fmt.Println("counted", n)
must(strg.Update(ctx, id, "", &book{
ID: "book-dune", Title: "Dune Messiah",
}, &update.Update{}))
fmt.Println("updated", id)
must(strg.Delete(ctx, id, "", &delete.Delete{}))
fmt.Println("deleted", id)
}Close() error is part of storage.IStorage, and every shipped adapter implements it. Close is idempotent: the first call attempts to release the resources the adapter owns, and later calls return nil.
Closing bolt is a correctness requirement. bbolt holds an exclusive lock on its database file while it is open, so an unclosed storage blocks every later Open of that path for the process lifetime.
list.List exposes Sort, Limit, Offset, and Fields. list.New() sets Limit to 10 by default. bolt, file, memory, redis, and sftp merge caller params over those defaults, then apply the merged values in the order sort, paginate, then project. A literal such as &list.List{Sort: ...} therefore inherits the default limit; a nil params pointer applies none of these four fields. memory.List has no defined order unless Sort is set because it ranges a sync.Map.
elasticsearch and mongodb translate all four fields. dynamodb sends Limit and a Fields projection to Scan, skips Offset items client-side while consuming paginated results, and does not use Sort. The SQL adapters execute Search as the query and do not translate the four structured fields. s3.List does not apply them.
BulkCreate is part of storage.IStorage, and all twelve adapters implement it. The implementations use multi-row INSERT for the SQL adapters, bounded bbolt write transactions for bolt, unordered InsertMany for mongodb, _bulk for elasticsearch, BatchWriteItem for dynamodb, and pipelining for redis. file and memory loop over single writes; s3 and sftp do the same with bounded concurrency. All use the same result contract.
Its contract is explicit:
- It is not atomic. Successful writes remain committed when other items fail, and a post-hook failure does not roll back the write.
- Processing order and result-slice order are not guaranteed. Backends may chunk, parallelize, or reorder work.
- Every input is reported exactly once by its original zero-based input index in either
BulkResult.SucceededorBulkResult.Failures; failures retain their underlying errors. - Any unsuccessful item produces an inspectable
*storage.BulkError. If processing stops, unattempted items are failures carrying the stopping error andBulkResult.Stoppedis true. - Empty input succeeds with an empty result, even with an already-cancelled context. IDs must be non-empty. For duplicate IDs, the value that remains is unspecified.
Inspect the result before retrying so already-successful writes are not repeated.
- Fork
- Clone
- Create a branch
- Make changes following the same standards as the project
- Run
make ci - Create a merge request