Skip to content

Commit dbba968

Browse files
rafamizesrultor
authored andcommitted
feat: JsonCache interface and Fake, Mem, Wrap impl
1 parent d1d22f1 commit dbba968

11 files changed

+222
-28
lines changed

README.md

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,39 +28,38 @@ Rultor.com](https://www.rultor.com/b/dartoos-dev/json_cache)](https://www.rultor
2828

2929
## Overview
3030

31-
**Json Cache** is an object-oriented package to serve as a layer on top of local
32-
storage packages, unifying them as an elegant caching API.
31+
**Json Cache** is an object-oriented layer on top of local storage packages to
32+
unify them as an elegant caching API.
3333

3434
In addition, this package gives developers great flexibility by providing a set
35-
of classes that can be selected and combined in various ways to meet specific
36-
requirements.
35+
of classes that can be selected and grouped in various combinations to meet
36+
specific cache requirements.
3737

3838
**Why Json?**
3939

4040
- Because most of the local storage packages available for Flutter applications
4141
use json as the data format.
42-
- There is an one-to-one relationship between the Dart's built-in type
43-
`Map<String, dynamic>` and json, which makes json encoding/decoding a
44-
trivial task.
42+
- There is a one-to-one relationship between Dart's built-in type `Map<String,
43+
dynamic>` and json, which makes encoding/decoding data in json a trivial task.
4544

4645
## Getting Started
4746

4847
`JsonCache` is the core interface of this package and represents the concept of
4948
cached data. It is defined as:
5049

5150
```dart
52-
/// Represents a cached json data.
51+
/// Represents data cached in json.
5352
abstract class JsonCache {
5453
/// Frees up cache storage space.
5554
Future<void> clear();
5655
57-
/// Refreshes some cached data by its associated key.
56+
/// Updates cached data found by its associated [key].
5857
Future<void> refresh(String key, Map<String, dynamic> data);
5958
6059
/// Erases [key] and returns its associated data.
6160
Future<Map<String, dynamic>?> erase(String key);
6261
63-
/// Recovers some cached data; null if a cache miss occurs - no [key] found.
62+
/// Recovers cached data by [key]; returns null if a cache miss occurs.
6463
Future<Map<String, dynamic>?> recover(String key);
6564
}
6665
```
@@ -96,3 +95,6 @@ flutter run -d chrome
9695
This should launch the demo application on Chrome in debug mode.
9796

9897
## References
98+
99+
- [Caching for objects](https://www.pragmaticobjects.com/chapters/012_caching_for_objects.html)
100+
- [Dart and race conditions](https://pub.dev/packages/mutex)

lib/json_cache.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
/// Collection of json cache decorators.
12
library json_cache;
23

3-
/// A Calculator.
4-
class Calculator {
5-
/// Returns [value] plus 1.
6-
int addOne(int value) => value + 1;
7-
}
4+
export 'src/json_cache.dart';
5+
export 'src/json_cache_fake.dart';
6+
export 'src/json_cache_mem.dart';
7+
export 'src/json_cache_wrap.dart';

lib/src/json_cache.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
/// Represents data cached in json.
2+
abstract class JsonCache {
3+
/// Frees up storage space.
4+
Future<void> clear();
5+
6+
/// Updates data at [key], or creates a new cache line at [key] if there is no
7+
/// previous data there.
8+
Future<void> refresh(String key, Map<String, dynamic> data);
9+
10+
/// Removes data from cache at [key] and returns it, or returns null if there
11+
/// is no data at [key].
12+
Future<Map<String, dynamic>?> erase(String key);
13+
14+
/// Retrieves either the data at [key] or null if a cache miss occurs.
15+
Future<Map<String, dynamic>?> recover(String key);
16+
}

lib/src/json_cache_fake.dart

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import 'package:json_cache/json_cache.dart';
2+
3+
/// In-memory cache. It is intended for unit testing and prototyping.
4+
///
5+
/// **Warning**: do not use it in production code. It is not thread safe.
6+
class JsonCacheFake implements JsonCache {
7+
/// Default ctor. Shares a static memory with other instances.
8+
JsonCacheFake() : this.mem(_shrMem);
9+
10+
/// Cache with custom memory.
11+
JsonCacheFake.mem(this._memory);
12+
13+
/// in-memory storage.
14+
final Map<String, Map<String, dynamic>?> _memory;
15+
16+
static final Map<String, Map<String, dynamic>> _shrMem = {};
17+
18+
/// Clears the internal map.
19+
@override
20+
Future<void> clear() async => _memory.clear();
21+
22+
/// Updates data located at [key].
23+
@override
24+
Future<void> refresh(String key, Map<String, dynamic> data) async =>
25+
_memory[key] = Map<String, dynamic>.of(data);
26+
27+
/// Removes data located at [key].
28+
@override
29+
Future<Map<String, dynamic>?> erase(String key) async => _memory.remove(key);
30+
31+
/// Retrieves the data at [key] or null if there is no data.
32+
@override
33+
Future<Map<String, dynamic>?> recover(String key) async {
34+
final cached = _memory[key];
35+
return cached == null ? cached : Map<String, dynamic>.of(cached);
36+
}
37+
}

lib/src/json_cache_mem.dart

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import 'package:mutex/mutex.dart';
2+
3+
import 'json_cache.dart';
4+
5+
/// Thread-safe, in-memory [JsonCache] decorator.
6+
///
7+
/// It is a kind of level 1 cache.
8+
///
9+
/// TODO: limit the maximum number of cache entries via "size" parameter in
10+
/// constructors.
11+
///
12+
/// It encapsulates a slower chache but keeps its own data in-memory.
13+
class JsonCacheMem implements JsonCache {
14+
/// Default ctor. [level2] the slower level 2 cache.
15+
JsonCacheMem(JsonCache level2) : this.main(level2, _shrMem, _shrMutex);
16+
17+
/// Cache with custom memory.
18+
JsonCacheMem.mem(JsonCache level2, Map<String, Map<String, dynamic>?> mem)
19+
: this.main(level2, mem, ReadWriteMutex());
20+
21+
/// Main ctor.
22+
JsonCacheMem.main(
23+
JsonCache level2,
24+
Map<String, Map<String, dynamic>?> mem,
25+
ReadWriteMutex mutex,
26+
) : _level2 = level2,
27+
_memory = mem,
28+
_mutex = mutex;
29+
30+
/// Slower cache level.
31+
final JsonCache _level2;
32+
33+
/// in-memory storage.
34+
final Map<String, Map<String, dynamic>?> _memory;
35+
36+
/// Mutex lock-guard.
37+
final ReadWriteMutex _mutex;
38+
39+
/// in-memory shared storage.
40+
static final Map<String, Map<String, dynamic>> _shrMem = {};
41+
42+
/// shared mutex.
43+
static final _shrMutex = ReadWriteMutex();
44+
45+
/// Frees up storage space in both the level2 cache and in-memory cache.
46+
@override
47+
Future<void> clear() async {
48+
await _mutex.protectWrite(() async {
49+
await _level2.clear();
50+
_memory.clear();
51+
});
52+
}
53+
54+
/// Updates data located at [key] in both the level2 cache and in-memory
55+
/// cache.
56+
@override
57+
Future<void> refresh(String key, Map<String, dynamic> data) async {
58+
/// ATTENTION: It is safer to copy the content of [data] before calling an
59+
/// asynchronous method that will copy it to avoid data races. For example,
60+
/// if the client code clears [data] right after passing it to this method,
61+
/// there's a high chance of having _level2 and this object with different
62+
/// contents.
63+
///
64+
/// In Dart, synchronous code cannot be interrupted, so there is no need to
65+
/// protect it using mutual exclusion.
66+
final copy = Map<String, dynamic>.of(data);
67+
await _mutex.protectWrite(() async {
68+
await _level2.refresh(key, copy);
69+
_memory[key] = copy;
70+
});
71+
}
72+
73+
/// Removes data located at [key] from both the level2 cache and in-memory
74+
/// cache.
75+
@override
76+
Future<Map<String, dynamic>?> erase(String key) async {
77+
return _mutex.protectWrite(() async {
78+
await _level2.erase(key);
79+
return _memory.remove(key);
80+
});
81+
}
82+
83+
/// Retrieves the data at [key] or null if there is no data.
84+
@override
85+
Future<Map<String, dynamic>?> recover(String key) async {
86+
return _mutex.protectRead(() async {
87+
if (!_memory.containsKey(key)) {
88+
_memory[key] = await _level2.recover(key);
89+
}
90+
final cached = _memory[key];
91+
return cached == null ? cached : Map<String, dynamic>.of(cached);
92+
});
93+
}
94+
}

lib/src/json_cache_wrap.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import 'json_cache.dart';
2+
3+
/// Decorator Envelope of [JsonCache].
4+
///
5+
/// It just forwards method calls to its encapsulated [JsonCache] instance.
6+
abstract class JsonCacheWrap implements JsonCache {
7+
/// Ctor.
8+
const JsonCacheWrap(this._wrapped);
9+
10+
// wrapped instance.
11+
final JsonCache _wrapped;
12+
13+
/// Forwards to its encapsulated [JsonCache] instance.
14+
@override
15+
Future<void> clear() => _wrapped.clear();
16+
17+
/// Forwards to its encapsulated [JsonCache] instance.
18+
@override
19+
Future<Map<String, dynamic>?> erase(String key) => _wrapped.erase(key);
20+
21+
/// Forwards to its encapsulated [JsonCache] instance.
22+
@override
23+
Future<Map<String, dynamic>?> recover(String key) => _wrapped.recover(key);
24+
25+
/// Forwards to its encapsulated [JsonCache] instance.
26+
@override
27+
Future<void> refresh(String key, Map<String, dynamic> data) =>
28+
_wrapped.refresh(key, data);
29+
}

pubspec.lock

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ packages:
77
name: async
88
url: "https://pub.dartlang.org"
99
source: hosted
10-
version: "2.7.0"
10+
version: "2.8.1"
1111
boolean_selector:
1212
dependency: transitive
1313
description:
@@ -81,6 +81,13 @@ packages:
8181
url: "https://pub.dartlang.org"
8282
source: hosted
8383
version: "1.7.0"
84+
mutex:
85+
dependency: "direct main"
86+
description:
87+
name: mutex
88+
url: "https://pub.dartlang.org"
89+
source: hosted
90+
version: "3.0.0"
8491
path:
8592
dependency: transitive
8693
description:
@@ -134,7 +141,7 @@ packages:
134141
name: test_api
135142
url: "https://pub.dartlang.org"
136143
source: hosted
137-
version: "0.4.1"
144+
version: "0.4.2"
138145
typed_data:
139146
dependency: transitive
140147
description:

pubspec.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ environment:
1111
dependencies:
1212
flutter:
1313
sdk: flutter
14+
mutex: ^3.0.0
1415

1516
dev_dependencies:
1617
flutter_test:

test/json_cache_mem_test.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import 'package:flutter_test/flutter_test.dart';
2+
import 'package:json_cache/json_cache.dart';
3+
4+
void main() {
5+
group('JsonCacheMem', () {
6+
test('clear', () async {
7+
final Map<String, Map<String, dynamic>> data = {
8+
'profile': <String, dynamic>{'id': 1, 'name': 'John Due'}
9+
};
10+
await JsonCacheMem.mem(JsonCacheFake.mem(data), data).clear();
11+
expect(data.isEmpty, true);
12+
});
13+
});
14+
}

test/json_cache_test.dart

Lines changed: 0 additions & 11 deletions
This file was deleted.

0 commit comments

Comments
 (0)