Simplified interface
This commit is contained in:
parent
ec5871289b
commit
47ec84e8ba
@ -54,6 +54,11 @@ func New[T any](namespace string, storage Storage[T]) (Collection[T], error) {
|
|||||||
return c, storage.Init(c)
|
return c, storage.Init(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewFile creates a new collection with the given name and the File storage system.
|
||||||
|
func NewFile[T any](namespace string) (Collection[T], error) {
|
||||||
|
return New[T](namespace, &File[T]{})
|
||||||
|
}
|
||||||
|
|
||||||
// All returns a channel of all objects in the collection.
|
// All returns a channel of all objects in the collection.
|
||||||
func (c *collection[T]) All() <-chan *T {
|
func (c *collection[T]) All() <-chan *T {
|
||||||
channel := make(chan *T)
|
channel := make(chan *T)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package storage
|
package ocean
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@ -10,8 +10,6 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.akyoto.dev/go/ocean"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -20,12 +18,12 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type File[T any] struct {
|
type File[T any] struct {
|
||||||
collection ocean.StorageData
|
collection StorageData
|
||||||
dirty atomic.Uint32
|
dirty atomic.Uint32
|
||||||
sync chan struct{}
|
sync chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fs *File[T]) Init(c ocean.StorageData) error {
|
func (fs *File[T]) Init(c StorageData) error {
|
||||||
fs.collection = c
|
fs.collection = c
|
||||||
fs.sync = make(chan struct{})
|
fs.sync = make(chan struct{})
|
||||||
|
|
@ -1,24 +1,18 @@
|
|||||||
package storage_test
|
package ocean_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"git.akyoto.dev/go/assert"
|
"git.akyoto.dev/go/assert"
|
||||||
"git.akyoto.dev/go/ocean"
|
"git.akyoto.dev/go/ocean"
|
||||||
"git.akyoto.dev/go/ocean/storage"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ ocean.Storage[string] = (*storage.File[string])(nil)
|
var _ ocean.Storage[string] = (*ocean.File[string])(nil)
|
||||||
|
|
||||||
type User struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFilePersistence(t *testing.T) {
|
func TestFilePersistence(t *testing.T) {
|
||||||
users, err := ocean.New[User]("test", &storage.File[User]{})
|
users, err := ocean.NewFile[User]("test")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
defer users.Sync()
|
|
||||||
defer users.Clear()
|
defer users.Clear()
|
||||||
|
|
||||||
users.Set("1", &User{Name: "User 1"})
|
users.Set("1", &User{Name: "User 1"})
|
||||||
@ -26,7 +20,7 @@ func TestFilePersistence(t *testing.T) {
|
|||||||
users.Set("3", &User{Name: "User 3"})
|
users.Set("3", &User{Name: "User 3"})
|
||||||
users.Sync()
|
users.Sync()
|
||||||
|
|
||||||
reload, err := ocean.New[User]("test", &storage.File[User]{})
|
reload, err := ocean.NewFile[User]("test")
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
user, err := reload.Get("1")
|
user, err := reload.Get("1")
|
28
README.md
28
README.md
@ -11,27 +11,29 @@ go get git.akyoto.dev/go/ocean
|
|||||||
## Example
|
## Example
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// Define the User type
|
// User type
|
||||||
type User struct { Name string }
|
type User struct { Name string }
|
||||||
|
|
||||||
// Create a collection in ~/.ocean/myapp/User.dat
|
// Create ~/.ocean/myapp/User.dat
|
||||||
users := ocean.New[User]("myapp", &storage.File[User]{})
|
users := ocean.NewFile[User]("myapp")
|
||||||
|
|
||||||
// Store some data
|
// Write
|
||||||
users.Set("1", &User{Name: "User 1"})
|
users.Set("1", &User{Name: "User 1"})
|
||||||
users.Set("2", &User{Name: "User 2"})
|
users.Set("2", &User{Name: "User 2"})
|
||||||
users.Set("3", &User{Name: "User 3"})
|
users.Set("3", &User{Name: "User 3"})
|
||||||
|
|
||||||
// Read from memory
|
// Read
|
||||||
first, err := users.Get("1")
|
a, err := users.Get("1")
|
||||||
|
b, err := users.Get("2")
|
||||||
|
c, err := users.Get("3")
|
||||||
|
|
||||||
// Iterate over all users
|
// Iterate
|
||||||
for user := range users.All() {
|
for user := range users.All() {
|
||||||
fmt.Println(user.Name)
|
fmt.Println(user.Name)
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## File format
|
## Format
|
||||||
|
|
||||||
```json
|
```json
|
||||||
1
|
1
|
||||||
@ -57,17 +59,17 @@ BenchmarkNew-12 48838576 22.89 ns/op 80 B/op
|
|||||||
|
|
||||||
You can specify `nil` as the storage system which will keep data in RAM only.
|
You can specify `nil` as the storage system which will keep data in RAM only.
|
||||||
|
|
||||||
### storage.File
|
### ocean.File
|
||||||
|
|
||||||
`storage.File` uses a single file to store all records.
|
`ocean.File` uses a single file to store all records.
|
||||||
Writes using `Set(key, value)` are async and only mark the collection as "dirty" which is very quick.
|
Writes using `Set(key, value)` are async and only mark the collection as "dirty" which is very quick.
|
||||||
The sync to disk happens shortly afterwards.
|
The sync to disk happens shortly afterwards.
|
||||||
Every collection uses one goroutine to check the "dirty" flag, write the new contents to disk and reset the flag.
|
Every collection uses one goroutine to check the "dirty" flag, write the new contents to disk and reset the flag.
|
||||||
|
|
||||||
The biggest advantage of `storage.File` is that it scales well with the number of requests:
|
The biggest advantage of `ocean.File` is that it scales well with the number of requests:
|
||||||
|
|
||||||
Suppose `n` is the number of write requests and `io` is the time it takes for one write. Immediate storage would require `O(n * io)` time to complete all writes but the async behavior makes it `O(n)`.
|
Suppose `n` is the number of write requests and `io` is the time it takes for one write. Immediate ocean would require `O(n * io)` time to complete all writes but the async behavior makes it `O(n)`.
|
||||||
|
|
||||||
You should use `storage.File` if you have a permanently running process such as a web server where end users expect quick responses and background work can happen after the user request has already been dealt with.
|
You should use `ocean.File` if you have a permanently running process such as a web server where end users expect quick responses and background work can happen after the user request has already been dealt with.
|
||||||
|
|
||||||
Make sure you `defer collection.Sync()` to ensure that queued writes will be handled when the process ends.
|
Make sure you `defer collection.Sync()` to ensure that queued writes will be handled when the process ends.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package storage
|
package ocean
|
||||||
|
|
||||||
type keyValue struct {
|
type keyValue struct {
|
||||||
key string
|
key string
|
Loading…
Reference in New Issue
Block a user