platform/packages/cache/lib/src/cache_service.dart

134 lines
3.7 KiB
Dart
Raw Normal View History

2018-04-02 02:58:16 +00:00
import 'dart:async';
2018-04-02 03:41:43 +00:00
import 'package:collection/collection.dart';
import 'package:angel3_framework/angel3_framework.dart';
2018-04-02 01:05:35 +00:00
/// An Angel [Service] that caches data from another service.
///
/// This is useful for applications of scale, where network latency
/// can have real implications on application performance.
2018-10-21 09:35:04 +00:00
class CacheService<Id, Data> extends Service<Id, Data> {
2018-04-02 01:05:35 +00:00
/// The underlying [Service] that represents the original data store.
2018-10-21 09:35:04 +00:00
final Service<Id, Data> database;
2018-04-02 01:05:35 +00:00
/// The [Service] used to interface with a caching layer.
///
/// If not provided, this defaults to a [MapService].
2018-10-21 09:35:04 +00:00
final Service<Id, Data> cache;
2018-04-02 01:05:35 +00:00
2018-11-10 16:59:07 +00:00
/// If `true` (default: `false`), then result caching will discard parameters passed to service methods.
///
/// If you want to return a cached result more-often-than-not, you may want to enable this.
final bool ignoreParams;
2018-04-02 03:41:43 +00:00
2021-06-22 10:42:26 +00:00
final Duration timeout;
2018-04-02 03:41:43 +00:00
2018-10-21 09:35:04 +00:00
final Map<Id, _CachedItem<Data>> _cache = {};
_CachedItem<List<Data>>? _indexed;
2018-04-02 03:41:43 +00:00
CacheService(
{required this.database,
required this.cache,
2021-06-22 10:42:26 +00:00
this.timeout = const Duration(minutes: 10),
this.ignoreParams = false});
2018-04-02 02:58:16 +00:00
2018-10-21 09:35:04 +00:00
Future<T> _getCached<T>(
2021-06-22 10:42:26 +00:00
Map<String, dynamic> params,
_CachedItem? Function() get,
FutureOr<T> Function() getFresh,
FutureOr<T> Function() getCached,
FutureOr<T> Function(T data, DateTime now) save) async {
2018-04-02 03:41:43 +00:00
var cached = get();
2021-06-22 10:42:26 +00:00
var now = DateTime.now().toUtc();
2018-04-02 03:41:43 +00:00
if (cached != null) {
// If the entry has expired, don't send from the cache
2021-06-22 10:42:26 +00:00
var expired = now.difference(cached.timestamp) >= timeout;
2018-04-02 03:41:43 +00:00
2021-06-22 10:42:26 +00:00
if (!expired) {
2018-04-02 03:41:43 +00:00
// Read from the cache if necessary
2018-11-10 16:59:07 +00:00
var queryEqual = ignoreParams == true ||
2021-06-22 10:42:26 +00:00
(cached.params != null &&
2018-10-21 09:35:04 +00:00
const MapEquality().equals(
2021-06-26 10:02:41 +00:00
params['query'] as Map, cached.params['query'] as Map));
2018-04-02 03:41:43 +00:00
if (queryEqual) {
return await getCached();
}
}
}
// If we haven't fetched from the cache by this point,
// let's fetch from the database.
var data = await getFresh();
await save(data, now);
return data;
}
@override
Future<List<Data>> index([Map<String, dynamic>? params]) {
2018-04-02 03:41:43 +00:00
return _getCached(
2021-06-22 10:42:26 +00:00
params ?? {},
2018-04-02 03:41:43 +00:00
() => _indexed,
() => database.index(params),
2018-10-21 09:35:04 +00:00
() => _indexed?.data ?? [],
2018-04-02 03:41:43 +00:00
(data, now) async {
2021-06-22 10:42:26 +00:00
_indexed = _CachedItem(params, now, data);
2018-04-02 03:41:43 +00:00
return data;
},
);
}
@override
Future<Data> read(Id id, [Map<String, dynamic>? params]) async {
2018-10-21 09:35:04 +00:00
return _getCached<Data>(
2021-06-22 10:42:26 +00:00
params ?? {},
2018-04-02 03:41:43 +00:00
() => _cache[id],
() => database.read(id, params),
() => cache.read(id),
(data, now) async {
2021-06-22 10:42:26 +00:00
_cache[id] = _CachedItem(params, now, data);
2018-04-02 03:41:43 +00:00
return await cache.modify(id, data);
},
);
}
2018-04-02 02:58:16 +00:00
@override
Future<Data> create(data, [Map<String, dynamic>? params]) {
2018-04-02 03:41:43 +00:00
_indexed = null;
2018-04-02 02:58:16 +00:00
return database.create(data, params);
}
@override
Future<Data> modify(Id id, Data data, [Map<String, dynamic>? params]) {
2018-04-02 03:41:43 +00:00
_indexed = null;
_cache.remove(id);
2018-04-02 02:58:16 +00:00
return database.modify(id, data, params);
}
@override
Future<Data> update(Id id, Data data, [Map<String, dynamic>? params]) {
2018-04-02 03:41:43 +00:00
_indexed = null;
_cache.remove(id);
2018-04-02 02:58:16 +00:00
return database.modify(id, data, params);
}
@override
Future<Data> remove(Id id, [Map<String, dynamic>? params]) {
2018-04-02 03:41:43 +00:00
_indexed = null;
_cache.remove(id);
2018-04-02 02:58:16 +00:00
return database.remove(id, params);
}
2018-04-02 01:05:35 +00:00
}
2018-04-02 03:41:43 +00:00
2018-10-21 09:35:04 +00:00
class _CachedItem<Data> {
2022-08-17 12:06:27 +00:00
final dynamic params;
2018-04-02 03:41:43 +00:00
final DateTime timestamp;
final Data? data;
2018-04-02 03:41:43 +00:00
_CachedItem(this.params, this.timestamp, [this.data]);
@override
String toString() {
return '$timestamp:$params:$data';
}
}