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)
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (c *collection[T]) All() <-chan *T {
|
||||
channel := make(chan *T)
|
||||
|
@ -1,4 +1,4 @@
|
||||
package storage
|
||||
package ocean
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
@ -10,8 +10,6 @@ import (
|
||||
"sort"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"git.akyoto.dev/go/ocean"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -20,12 +18,12 @@ const (
|
||||
)
|
||||
|
||||
type File[T any] struct {
|
||||
collection ocean.StorageData
|
||||
collection StorageData
|
||||
dirty atomic.Uint32
|
||||
sync chan struct{}
|
||||
}
|
||||
|
||||
func (fs *File[T]) Init(c ocean.StorageData) error {
|
||||
func (fs *File[T]) Init(c StorageData) error {
|
||||
fs.collection = c
|
||||
fs.sync = make(chan struct{})
|
||||
|
@ -1,24 +1,18 @@
|
||||
package storage_test
|
||||
package ocean_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.akyoto.dev/go/assert"
|
||||
"git.akyoto.dev/go/ocean"
|
||||
"git.akyoto.dev/go/ocean/storage"
|
||||
)
|
||||
|
||||
var _ ocean.Storage[string] = (*storage.File[string])(nil)
|
||||
|
||||
type User struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
var _ ocean.Storage[string] = (*ocean.File[string])(nil)
|
||||
|
||||
func TestFilePersistence(t *testing.T) {
|
||||
users, err := ocean.New[User]("test", &storage.File[User]{})
|
||||
users, err := ocean.NewFile[User]("test")
|
||||
assert.Nil(t, err)
|
||||
|
||||
defer users.Sync()
|
||||
defer users.Clear()
|
||||
|
||||
users.Set("1", &User{Name: "User 1"})
|
||||
@ -26,7 +20,7 @@ func TestFilePersistence(t *testing.T) {
|
||||
users.Set("3", &User{Name: "User 3"})
|
||||
users.Sync()
|
||||
|
||||
reload, err := ocean.New[User]("test", &storage.File[User]{})
|
||||
reload, err := ocean.NewFile[User]("test")
|
||||
assert.Nil(t, err)
|
||||
|
||||
user, err := reload.Get("1")
|
28
README.md
28
README.md
@ -11,27 +11,29 @@ go get git.akyoto.dev/go/ocean
|
||||
## Example
|
||||
|
||||
```go
|
||||
// Define the User type
|
||||
// User type
|
||||
type User struct { Name string }
|
||||
|
||||
// Create a collection in ~/.ocean/myapp/User.dat
|
||||
users := ocean.New[User]("myapp", &storage.File[User]{})
|
||||
// Create ~/.ocean/myapp/User.dat
|
||||
users := ocean.NewFile[User]("myapp")
|
||||
|
||||
// Store some data
|
||||
// Write
|
||||
users.Set("1", &User{Name: "User 1"})
|
||||
users.Set("2", &User{Name: "User 2"})
|
||||
users.Set("3", &User{Name: "User 3"})
|
||||
|
||||
// Read from memory
|
||||
first, err := users.Get("1")
|
||||
// Read
|
||||
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() {
|
||||
fmt.Println(user.Name)
|
||||
}
|
||||
```
|
||||
|
||||
## File format
|
||||
## Format
|
||||
|
||||
```json
|
||||
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.
|
||||
|
||||
### 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.
|
||||
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.
|
||||
|
||||
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.
|
||||
|
@ -1,4 +1,4 @@
|
||||
package storage
|
||||
package ocean
|
||||
|
||||
type keyValue struct {
|
||||
key string
|
Loading…
Reference in New Issue
Block a user