ivoleitao / stash

Key-value store abstraction with plain and cache driven semantics and a pluggable backend architecture.
MIT License
87 stars 16 forks source link

Is it safe to open multiple stores? #33

Closed Prn-Ice closed 2 years ago

Prn-Ice commented 2 years ago

This is a question

Currently I have an app with multiple repositories

For example my new local data source for my account_repository will look like this.

final accountStore = await newObjectboxLocalCacheStore(
    fromEncodable: (json) => Account.fromJson(json),
  );

  // Creates a cache with a capacity of 10 from the previously created store
  final cache = await accountStore.cache<Account>(
    name: 'cache1',
    maxEntries: 10,
    eventListenerMode: EventListenerMode.synchronous,
  );

Is it safe to do this in all my repositories or is it more efficient to have one store shared through the entire app?

ivoleitao commented 2 years ago

In my honest opinion it’s probably more performant and memory efficient to use just one store However it all depends on your app constraints as it could make sense to separate. I wouldn’t do it though without a strong reason. This is valid mostly for persistent backends something like memory is very light

For example I have one app where I use a vault just to store assets ( images / binaries etc ) and a custom database for the metadata.

It all depends on your scenario but I don’t see any technical constraints to do not do it

Prn-Ice commented 2 years ago

If I did use one store wouldn't i loose the automatic deserialization?

ivoleitao commented 2 years ago

Ha I see it’s different types ? Meaning you need different fromEncodable functions per store ?

If thats the case yes you will need different stores. I have to say that this is a limitation of the library itself as in theory I should be able to allow the configuration of multiple fromEncodable functions per store name ( partitions ). I will consider the implications of doing that, seems a nice feature to add.

Answering directly with the current implementation, you need multiple stores if you are storing different object types. Bear in mind that even on a scenario where I hypothetically support multiple fromEncodable functions per store partition you would always loose the generic option as you will not be able to specify a type (as you can have multiple underneath ) when creating a vault/cache or a store

Prn-Ice commented 2 years ago

I see, so should I keep the issue open till then?

ivoleitao commented 2 years ago

Yes let met look at this as it seems fairly easy to do. However something tells me that at some point in time I had already looked at this but for some reason I didn't implemented it.

Let me look at it on this weekend to see if there's any blocker for this work

ivoleitao commented 2 years ago

Hi from what I was able to investigate this is possible to do. I will add this feature at vault / cache creation. I still need a couple of days to do this due to the number places that I have to change

Prn-Ice commented 2 years ago

That's awesome news, I'll be ready to test anytime.

ivoleitao commented 2 years ago

Hi, development is completed. This will unfortunately bring a small breaking change but honestly it was a miss and having this makes the library much better. Find a complete example bellow and notice that the fromEncodable function is configured per partition and not per store as previously

```dart
import 'dart:io';

import 'package:stash/stash_api.dart';
import 'package:stash_file/stash_file.dart';

class Task {
  final int id;
  final String title;
  final bool completed;

  Task({required this.id, required this.title, this.completed = false});

  /// Creates a [Task] from json map
  factory Task.fromJson(Map<String, dynamic> json) => Task(
      id: json['id'] as int,
      title: json['title'] as String,
      completed: json['completed'] as bool);

  /// Creates a json map from a [Task]
  Map<String, dynamic> toJson() =>
      <String, dynamic>{'id': id, 'title': title, 'completed': completed};

  @override
  String toString() {
    return 'Task $id, "$title" is ${completed ? "completed" : "not completed"}';
  }
}

class Contact {
  final int id;
  final String name;

  Contact({required this.id, required this.name});

  /// Creates a [Contact] from json map
  factory Contact.fromJson(Map<String, dynamic> json) =>
      Contact(id: json['id'] as int, name: json['name'] as String);

  /// Creates a json map from a [Contact]
  Map<String, dynamic> toJson() => <String, dynamic>{'id': id, 'name': name};

  @override
  String toString() {
    return 'Contact $id, "$name"';
  }
}

void main() async {
  // Temporary directory
  final path = Directory.systemTemp.path;

  // Creates a store
  final store = await newFileLocalVaultStore(path: path);

  // Creates a vault that stores Tasks from the previously created store
  final taskVault = await store.vault<Task>(
      name: 'taskVault',
      fromEncodable: (json) => Task.fromJson(json),
      eventListenerMode: EventListenerMode.synchronous)
    ..on<VaultEntryCreatedEvent<Task>>().listen(
        (event) => print('Key "${event.entry.key}" added to the task vault'));

  // Creates a vault that stores Contacts from the previously created store
  final contactVault = await store.vault<Contact>(
      name: 'contactVault',
      fromEncodable: (json) => Contact.fromJson(json),
      eventListenerMode: EventListenerMode.synchronous)
    ..on<VaultEntryCreatedEvent<Contact>>().listen((event) =>
        print('Key "${event.entry.key}" added to the contact vault'));

  // Adds a task with key 'task1' to the vault
  await taskVault.put('task1',
      Task(id: 1, title: 'Run task vault store example', completed: true));

  // Adds a contact with key 'contact1' to the vault
  await contactVault.put(
      'contact1', Contact(id: 1, name: 'Run contact vault store example'));

  // Retrieves the value from the task vault
  print(await taskVault.get('task1'));

  // Retrieves the value from the contact vault
  print(await contactVault.get('contact1'));
}

I will release it today.

Note: I didn't had time to develop automatic unit tests for this feature as the existing ones are per type and this needs to test multi-types. I will add it later

Prn-Ice commented 2 years ago

Thanks, currently on holiday, will test tomorrow

Prn-Ice commented 2 years ago

Seems to work just fine, want me to leave the issue open until you find time to add those tests?

ivoleitao commented 2 years ago

Thanks you can close it. I will try to add it in a future release