Simplified interface

This commit is contained in:
Eduard Urbach 2023-07-17 21:57:14 +02:00
parent ec5871289b
commit 47ec84e8ba
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
6 changed files with 28 additions and 29 deletions

View File

@ -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)

View File

@ -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{})

View File

@ -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")

View File

@ -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.

View File

@ -1,4 +1,4 @@
package storage package ocean
type keyValue struct { type keyValue struct {
key string key string