From 47ec84e8ba639a5d64a6d23c88595167bfcb5294 Mon Sep 17 00:00:00 2001 From: Eduard Urbach Date: Mon, 17 Jul 2023 21:57:14 +0200 Subject: [PATCH] Simplified interface --- Collection.go | 5 +++++ storage/File.go => File.go | 8 +++----- storage/File_test.go => File_test.go | 14 ++++---------- Errors.go => KeyNotFoundError.go | 0 README.md | 28 +++++++++++++++------------- storage/keyValue.go => keyValue.go | 2 +- 6 files changed, 28 insertions(+), 29 deletions(-) rename storage/File.go => File.go (95%) rename storage/File_test.go => File_test.go (67%) rename Errors.go => KeyNotFoundError.go (100%) rename storage/keyValue.go => keyValue.go (76%) diff --git a/Collection.go b/Collection.go index 5dde7fa..3411b60 100644 --- a/Collection.go +++ b/Collection.go @@ -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) diff --git a/storage/File.go b/File.go similarity index 95% rename from storage/File.go rename to File.go index e5bd347..1782e0b 100644 --- a/storage/File.go +++ b/File.go @@ -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{}) diff --git a/storage/File_test.go b/File_test.go similarity index 67% rename from storage/File_test.go rename to File_test.go index 7e7ae95..24cf118 100644 --- a/storage/File_test.go +++ b/File_test.go @@ -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") diff --git a/Errors.go b/KeyNotFoundError.go similarity index 100% rename from Errors.go rename to KeyNotFoundError.go diff --git a/README.md b/README.md index 14bdb46..99c8953 100644 --- a/README.md +++ b/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. diff --git a/storage/keyValue.go b/keyValue.go similarity index 76% rename from storage/keyValue.go rename to keyValue.go index 06ba39c..7e4d60d 100644 --- a/storage/keyValue.go +++ b/keyValue.go @@ -1,4 +1,4 @@ -package storage +package ocean type keyValue struct { key string