To provide the ability to serve results from a cache, instead of executing the governed func.
Func<TResult>), not void (Action) executions.The cache item expiry duration would be configured on the CachePolicy at configuration time, not passed at execution time:
CachePolicy in line with the Polly model, where the behavioural characteristics of policies are defined at configuration time, not call time. This makes for a cache policy with given behaviour which can be shared across calls..Execute() overloads on CachePolicy would present problems for the integration of CachePolicy into PolicyWrap. (The PolicyWrap (was Pipeline) feature in its proposed from requires that all policies have common .Execute() etc overloads.) CachePolicy<TResult> cachePolicy = Policy
.Cache<TResult>(TimeSpan slidingExpirationTimespan);
Users may want more control over caching characteristics or to use an alternative cache provider (Http cache or third-party). The following overload is also proposed:
CachePolicy<TResult> cachePolicy = Policy
.Cache<TResult>(IResultCacheProvider<TResult> cacheProvider);
where:
// namespace Polly.Cache
interface IResultCacheProvider<TResult>
{
TResult Get(Context);
void Put(Context, TResult);
}
Context itself is not the key; it is an execution context that travels with each Execute invocation on a Polly policy. Implementations should derive a cache key to use from elements in the Context. The usual cache key would be Context.ExecutionKey. See #139 IResultCacheProvider Get/Put signatures around Context rather than a string cacheKey allows implementers power to develop a more complex caching strategy around other keys or user information on the Context.// TResult form
TResult result = cachePolicy
.Execute(Func<TResult> executionFunc, new Context(executionKey)); // The executionKey is the cacheKey. See keys proposal.
(and other similar existing overloads taking a Context parameter)
The proposed implementation for the simple cache configuration overload is to use System.Runtime.Memory.MemoryCache.Default and the configured Timespan slidingExpirationTimespan to create a Polly.Cache.MemoryCacheProvider<TResult> : Polly.Cache.IResultCacheProvider<TResult>.
InvalidCastException if the value in the cache cannot be cast to TResultexecutionFunc if (and only if) a value could not be returned from cache.executionFunc, caches it under the given cache key for the given timespan.Comments? Alternative suggestions? Extra considerations to bear in mind?
Have scenarios to share where you'd use this? (could inform development)
Are the ExecuteAndCapture variants going to be supported?
Are the ExecuteAndCapture variants going to be supported?
Yes - good catch. And since the implementation obviously checks for a cached value before executing the user delegate, makes no difference to the operation of the CachePolicy whether any fault from the func is rethrown/passed to more outer layers, or captured.
I'm happy to work on this, as we will need this for a project I'm working on.
@SeanFarrow Sounds good! Many thanks for your involvement and offering to work up a PR on this! Please do come back to me questions / comments etc as they arise.
Ok, I will. I’m able to start working on this at the end of the month, I’ve got a major project to finish first!
What caches do we want, I’m thinking, Redis/Memcached, and the .net memory cache as well as maybe a disc based cache, with the memory cache being the default. Any other caches/thoughts?
I’m able to start working on this at the end of the month
Sure @SeanFarrow . Thanks for all your support and involvement!
What caches do we want, I’m thinking, Redis/Memcached, and the .net memory cache
as well as maybe a disc based cache, with the memory cache being the default.
Any other caches/thoughts?
All sound like good options!
And with the proposed IResultCacheProvider<TResult> interface, people can of course easily implement others.
Given we'd likely want to avoid taking dependencies on all these in the main Polly package, the individual cache implementations (default memory cache excepted) would probably go out as separate nuget packages Polly.Cache.Redis, Polly.Cache.Memcached etc, do you think? Unless they were each individually so small (in terms of code lines) that providing a wiki page for each was just as much an option - to decide later?
@_community_ : other caches you'd like to see supported?
Definitely separate NuGet packages. That makes more sense in terms of dependencies, as each cache is likely to depend on other NuGet packages. Also, they can then be updated out of band of the main poly package assuming of course that the interface doesn’t change!
I’m also thinking about a Poly.Cache.Core package containing the interface and the memory cache, as per my understanding, the memory cache is baked in to the .net framework—correct me if I’m wrong!
In terms of other caches we may want to support, maybe azure cache/amazon elastic cache, I need to check whether the latter has an api specifically, or whether it’s just memcached compatible.
@SeanFarrow All sounds good.
Only thought: maybe the default MemoryCache option (and IResultCacheProvider<TResult> interface) can just be part of the main Polly package, so that the CachePolicy (based on the MemoryCache default) works out-of-the-box with just the main Polly package. Yes, MemoryCache is part of the runtime at System.Runtime.Caching.MemoryCache
Ok, fair point, we’ll go with that then!
What version of .net are we supporting? I notice that poly supports .net 3.5, but the memory cache is 4.0+.
Do people see this as an issue? if so, what should we do for caching in .net 3.5?
Good question. Per #142 we will discontinue .NET3.5 support from Polly v5.0.0, as a number of the other new policies require facilities not in .NET3.5 either.
Ok, cool, can you assign me to the cache policy then?
Hey @SeanFarrow . Hmm. Seems from the github instructions I can't add you with the assignees button (same for Jerome and Bruno) because its scope is limited to AppvNext org members (since AppvNext also broader than Polly, not my position just to add you to AppvNext). No reflection on the importance of your contribution to Polly (great to have you involved!). Consider this assigned. Added the in progress label to indicate that it is spoken for!
Ok, thanks!
Given we're supporting async variants of execute, should we have an async cache provider as well?
Also, if the cache doesn't support synchronous functionality, what should our default position be?
Hey @SeanFarrow Great questions. What were your thoughts?
Some thoughts:
Given we're supporting async variants of execute, should we have an async cache provider as well?
[1] Yes good call. Polly having an async cache provider so that async executions through Polly can take advantage of where 3rd-party caches have (mature/stable) async APIs –> we should do that. Feels like two separate interfaces in Polly for sync and async providers, ie IResultCacheProvider<TResult> and IResultCacheProviderAsync<TResult>? That what you thinking? Async interface sthg like:
// namespace Polly.Cache
interface IResultCacheProviderAsync<TResult>
{
TResult GetAsync(Context); // should return: Task<TResult>
void PutAsync(Context, TResult); // should return: Task
}
_NB_ If you think the design of the IResultCacheProvider/Async<TResult> interfaces can be refined, feel free to say.
[2] The config overloads .CacheAsync<TResult>(...) configuring Polly’s _async_ cache policies should probably provide options to take either a sync or an async cache provider tho. Because there might be some cache providers which only have sync APIs but we still want to offer them to async cache policies? (MemoryCache.Default is in this category?)
So:
// Async policy taking async cache provider
CachePolicy<TResult> asyncCachePolicy = Policy
.CacheAsync<TResult>(IResultCacheProviderAsync<TResult> cacheProviderAsync);
// Async policy taking sync cache provider
CachePolicy<TResult> asyncCachePolicy = Policy
.CacheAsync<TResult>(IResultCacheProvider<TResult> cacheProvider);
Re:
if the cache doesn't support synchronous functionality, what should our default position be?
Again, really interested to hear your views. Thinking aloud from my side:
[3] The config overloads configuring Polly's _sync_ cache policy should probably only take _sync_ cache providers. IE just:
// Sync policy taking sync cache provider
CachePolicy<TResult> cachePolicy = Policy
.Cache<TResult>(IResultCacheProvider<TResult> cacheProvider);
(The opposite – providing an overload for a _sync_ cache policy taking an _async_ provider – feels like creating a potentially confusing API. Particularly, there’s a risk people would mistake that syntax for giving them the benefits of async behaviour, when it’d not be: it’d have to be blocking on the calls to the async cache provider to bring it into a sync policy/sync call, no?)
[4] But we could allow the use of third-party caches with async-only interfaces, in Polly’s _sync_ CachePolicys, if desired, by providing an implementation fulfilling Polly’s IResultCacheProvider<TResult> sync interface, just .Wait()-ing (or equiv) on the async calls. (And NB documenting that this is what it does!). Arguments for / against doing that? Doing it that way round, at least there’s no mistaking from the API we provide that you’re getting blocking/sync behaviour.
Hmm. The choice of C# clients for some of these caches has moved on since I was last involved, in some cases. Which of the 3rd-party cache's are currently offering an async-only (no sync) API?
Thoughts on all this?
Ok, thinking out loud:
In my mind the AsyncCachePolicy
Agree with 2, 3 and 4.
I haven’t checked specifics as yet, but generally anything cloud based will offer async and may offer sync, but they are moving towards the former only fairly rapidly.
Also, whilst I think about it, how do we want to handle the conversion from the cache to the TResult type?
Sometimes it may not be as straightforward as doing new T, should we offer the capability to define a delegate/lambda, or a conversion interface?
I’ve got a situation for example where I’m storing a base64-encoded compressed file (zip in this case), so I can’t just do new ZipArchive, or the equivalent, it needs an extra processing step!
Also, this may be valid, if you are storing the content of a web response a an array of bytes.
Thoughts…?
In my mind the AsyncCachePolicy
should
return a Taskand Task for get/put methods respectively.
Oops on my part: yes definitely!
(more on other q later)
Hey @SeanFarrow . Great to have all this on the cache policy!
Re:
how do we want to handle the conversion from the cache to the TResult type?
[...] should we offer the capability to define a delegate/lambda, or a conversion interface?
Where were you thinking this would sit in the architecture? As part of the CachePolicy configuration overloads, or in the IResultCacheProvider implementations?
My instinct is to keep the main CachePolicy configuration overloads simple-as-possible, ie we have the TimeSpan varieties plus:
.Cache<TResult>(IResultCacheProvider<TResult> cacheProvider) [and]
.CacheAsync<TResult>(IResultCacheProviderAsync<TResult> cacheProviderAsync)
rather than extend those with additional:
.Cache<TResult, TCachedFormat>(IResultCacheProvider<TResult> cacheProvider, ICacheValueFormatter<TResult, TCachedFormat> cacheValueFormatter) [etc]
(The formatter probably makes sense for some kinds of cache but not others. And: IResultCacheProvider<TResult> cacheProvider feels like the correct scope of interface to configure a CachePolicy ... for the policy to use the cache, all you need to know is that you can get and put in and out of it ... if some cache implementations prefer to compress or map to a more cloud-friendly format, that feels like a cache implementation concern. )
So thinking of it structurally as a cache implementation concern, my instinct is for the transform-for-caching functionality being part of IResultCacheProvider/Async implementations where needed, config'd on them where needed.
Sound sensible? / can you see disadvantages? / or just stating the obvious??
:+1: re conversion interface. If we went as above ... and if there were a _group_ of cache implementations (cloud caches?) where this approach might be particularly useful, one could still eg structure that with an abstract base class taking a conversion interface like you say, and some cache implementations deriving from that ...
Further thoughts? (You deep in and may see other angles! )
I agree with you re scoping.
It may be that certain keys are compressed/others are serialized in different ways, so we may not be able to use a base class here, we could put an ICacheOutputConverter
Finally, Bear in mind that converting a value might not be straightforward, take the case where you have cached some compressed data, to decompress this data might require more than just calling a class constructor, you may need to read from a memory stream for example.
Thoughts…?
hey @SeanFarrow Great qs. Completely with you about needing conversion funcs rather than new-ing items out of cache. (Defined on an ICacheOutputConverter<TResult> interface or similar like you suggest sounds good!)
How do you see this:
we could put an ICacheOutputConverter
interface as part of the get/put calls,
defaulting to null. If the converter is null we just use the default which does a new T.
looking in actual code? We'd need to avoid the various gotchas flowing from having optional parameters in interfaces (like the default values in the interface taking precedence over values in any implementations of the interface, if the call is being made against the interface not an implementation), but maybe that is not what you were thinking anyway?
Hadn’t thought of that!
OK, how about having a SetCacheOutputConverter on the cache interface?
how about having a SetCacheOutputConverter on the cache interface?
Mutable policies by property-injection/setter-method injection a possible trap for the unwary in highly concurrent / multi-threaded scenario? (Setting output converter then executing not atomic; risk some thread sets the cache output converter while another thread is mid executing?). (Might not be the way we envisage it being used, but opens up the possibility)
Constructor-injection somewhere (resulting in immutable policy) safer? ICacheOutputConverter<TResult> could perhaps be constructor-injected into the class fulfilling IResultCacheProvider/Async? What do you think?
Possibly, yes, but what if, I want a different converter per type?
We will need to support passing in an Ienumerable of converters.
what if I want a different converter per type?
Good point!
We will need to support passing in an Ienumerable of converters.
Hmm. Were you thinking this maybe isn't ideal? Could we do better somehow?
.
I am wondering if one of us throwing together a quick class/interface diagram (as a 'straw man' ...) would help at this point? (getting hard to envision sentence-wise). A class diag to pull about could help sort out the different components, role they play and their multiplicity/scope.
_Great_ to have all your deep input on these caching qs!
@seanfarrow Stepping back and thinking about converters-for-caches, I wonder if we are missing something simple: just a minimal adapter pattern?
Assuming the previous IResultCacheProvider<TResult> and (for discussion; say if you think different) ... a format-conversion interface like below:
interface ICacheFormatConverter<TResult, TCacheFormat> {
TCacheFormat Encode(TResult result);
TResult Decode(TCacheFormat cachedValue);
}
Format converters could be provided via a lightweight adapter:
public class CacheWithConversion<TResult, TCacheFormat>(ICacheFormatConverter<TResult, TCacheFormat> converter, IResultCacheProvider<TCacheFormat> wrappedCache) : IResultCacheProvider<TResult>
{
public TResult Get(Context context)
{
return converter.Decode(wrappedCache.Get(context));
};
public void Put(Context context, TResult result)
{
wrappedCache.Put(Context, converter.Encode(result));
}
}
?
If this still doesn't seem to join with your thoughts on cache providers so far, then possibly we have a different vision of how the contributing classes interact and their multiplicity: let's dig deeper if so.
(Not saying this is the only solution - there could be others - but one way to deal with the multiplicity problem ("different converter per type") without any enumerable?)
Hi,
That makes sense, I had visions of people being able to supply the cache to wrap the converter with at runtime.
With your proposal, wouldn’t we need converters for each cache type?
Hi @SeanFarrow . Re:
I had visions of people being able to supply the cache to wrap the converter with at runtime.
(I haven’t quite followed here. Do you think the lightweight wrapper proposal is good in this respect, or is there an at-runtime capability we are missing?)
Re:
With your proposal, wouldn’t we need converters for each cache type?
I am wondering if some of the questions around multiplicity are stemming from the original proposal for the IResultCacheProvider<TResult> to be strongly-typed.
Are you thinking that rather than IResultCacheProvider
interface IResultCacheProvider
{
object Get(Context);
void Put(Context, object);
}
and thus the converter interface only ICacheFormatConverter<TResult>?
(just wanting to understand clearly at this point- can then think through in context of rest of design)
Thanks!
I think if we use a non-generic cache interface, this would make the converters easier. That way, people could have a converter if they want, What I’m thinking is that a converter would know what type it converts to, and has a CanHandle message.
Re:
I think if we use a non-generic cache interface, this would make the converters easier
Thanks @SeanFarrow, this is well worth thinking about . I now need to spend some time on the PolicyWrap, to explore mixing strongly-typed/generic and non-generic policies, which is related.
@SeanFarrow , re:
What I’m thinking is that a converter would know what type it converts to,
and has a CanHandle message
How does the CanHandle method(?) come into play? Does the CanHandle method imply a list of converters registered somewhere (for example on the CacheProvider), that some cache logic tries in turn until it finds one that 'can handle' in the context of the execution-in-process?
(If the converter was wrapping the cache provider supplied to CachePolicy, per my example a day or so ago, a CanHandle method wouldn't be necessary? - it just applies because it is part of the cache policy for that call.)
Yep, that’s what I was thinking.
Per your example a day or so ago if the cache policy was wrapped by a converter, we could have only one converter per cache policy? Correct me if I’m wrong?
That’s the limitation I’m trying to avoid as I see myself and others storing items needing more than one conversion strategy in the same cache.
@SeanFarrow I'm feeling the need for some code samples / a 'straw man', to get a clearer shared understanding of any proposal being discussed.
I am putting something together in a repo entirely separate from Polly.
Feel free also to set any architectural sketch down in code! (it would give comparative perspectives to discuss)
(All Polly activity has to fall outside work time for me, but pushing this along - it would be good to progress this feature!).
Let me see what you have first, snowed under work wise as you are, plus I’m disappearing for a month for another job!
@SeanFarrow This branch contains a proposed cache architecture for Polly emerging from our discussion.
To summarise its take on various points discussed in the past:
CachePolicy which can generic .Execute<TResult>() for any TResult, rather than (as was once mooted) forcing every CachePolicy to be a strongly-typed CachePolicy<TResult>. This will allow for more re-use of the same CachePolicy instance across multiple PolicyWraps / calls returning different TResult. [*]CachePolicy is applied (perhaps as part of a wrap) to a void-returning ActionICacheProvider and ICacheProviderAsync interfaces rather than combining them. Segregating the sync/async interfaces means we _enable but don't force_ implementers to provide an async-over-sync or sync-over-async implementation if desired (_for the cases where underlying cache supplies only one of sync or async_).Context to cache key is segregated into a new, separate interface ICacheKeyStrategy. A DefaultCacheKeyStrategy is provided, taking Context.ExecutionKey as the cache key, but users can implement their own ICacheKeyStrategy if they want to form a key in a more sophisticated manner from context data. A good example might be Polly protecting 'get-by-id' style calls to an underlying db, where cache keys including the record id might be desired.ICacheProvider.@Reisenberger,
This looks good.
Should we move this in to the v5-alpha branch, or a branch off it? I notice not all the tests are there, that are in the v5 branch.
What caches do people want to start with?
Cheers
Sean.
@SeanFarrow It _should_ have all the tests in the v5.0-alpha branch, as it was a recent sub-branch off it. Can you name a missing test, and I'll look more deeply?
@SeanFarrow Re branching, I think we can merge it into v5.0-alpha as soon as you confirm you also like / happy with architecture. _I will raise a few additional qs for reflection tomorrow_
Re architecture for implementing individual caches, I think we had then discussed packaging (... /cc @joelhulen I want you to be aware of this too ...) :
Polly.Cache.Redis. This would eg contain a Polly.Caching.RedisCacheProvider (fulfilling Polly.Caching.ICacheProvider) and anything associated.Polly.Cache or Polly.Cache.Core package which might provide some common facilities such as cache-mappers we deem useful across a range of cache providers. Or perhaps those could simply be kept in the core Polly package?Views on the package structuring welcome.
Hi,
All tests were there.
How do we go about testing cache providers? I suppose we could use something like Packer to build boxes, but we’d need a specific category of tests as these might be slower and take a while.
Architecture seems fine, I’m not in favour of keeping things in the core polly package, as some people just might not want any caching. A package such as Polly.Caching.Util probably seems more appropriate?
Probably more later…
@SeanFarrow I'll aim for a merge into the v5-alpha branch after work hours today.
Two questions: (feedback from anyone as we proceed welcome)
[1] The architecture leaves items-to-be-cached as object (ie ICacheProvider is non-generic), in order to avoid _forcing_ users to create a separate ICacheProvider<TResult> instance (and thus a separate CachePolicy<TResult> instance) for each TResult type they want to handle. This corresponds to many cache interfaces (eg MemoryCache) similarly just taking object, and promotes easier reuse of a CachePolicy instance: it might feature in a PolicyWrap used across multiple TResult types.
A downside is that this loose-typing leaves the signature of mappers in MappingCacheProvider as mapping between object and object.
_We should keep considering whether ICacheProvider<TResult> should be added_: feedback welcome from anyone engaging with this.
[2] Naming for mapper methods: The MappingCacheProvider class currently has PutMapper() and GetMapper() as the names (respectively) for the methods mapping values before placing in the cache, and after getting back from cache. _Suggestions for better names welcome_. I had Encode/Decode at one point...
@reisenberger,
with regards to 1, I still think a generic interface would be good, the only concern is as you say what this does to policy wrap, could we not have a wrapper type, or do something like outcome.net does?
Would it be better to have different types for input/output mapping from a cache, or at least abstract classes that implement the same interface?
@SeanFarrow Re:
could we not have a wrapper type, or do something like outcome.net does?
... I haven't quite followed: can you elaborate? Or, small code sketch?
I have overnight worked up a version adding in a generic ICacheProvider<TResult>, strongly-typed mappers, and an .As<T> syntax similar to Redis's, to provide a light wrapper and frictionless transition from non-generic to generic where needed. I'll push this later today for consideration.
how do you see the generic types being used? Does this mean will be duplicating code?
@seanfarrow I hadn’t seen the existence of the generic variants as creating code duplication. They just offer strongly-typed variants for those who want to code that way. For instance, they allow users to code a MappingCacheProvider<TNative,TMapped> if they want, rather than the mappings object<->object (or having to code if (TResult is TypeOfInterest) { /* do mapping stuff */ }) implied by a non-typed ICacheProvider.
ICacheProvider implementation for each cache implementation. eg RedisCacheProvider : ICacheProvider.As<TResult> extension method would provide a lightweight wrapper to return a ICacheProvider<TResult>, if wanting to work with a strongly-typed variant, eg to work with strongly-typed mappers.Policy.Cache<TResult>(ICacheProvider provider) config overload will create a strongly-typed CachePolicy<TResult> if desired.Some users will prefer to work with a CachePolicy<TResult> (gives Visual Studio type-binding/intellisense between the various type-bound usages of Polly they might be combining).
Other users will prefer to work with a non-generic CachePolicy that they can use across all types, but they don’t get the IDE-time type-sensitivity, or strongly-typed mappers.
In branch Cache-architectureTypingExperiment, I pushed this code which allows for both non-generic and generic variants.
[ EDIT: branch shows changes only for sync forms while discussion; easy to add similar async. ]
What do you think? Where shall we go from here?
_Very_ open to other suggestions, but I may need to see some more worked code sketches to understand what you may be thinking of?
Re:
Would it be better to have different types for input/output mapping from a cache[...]?
An advantage of both mappings (the mappings both ways) in the same class (as currently) is that - especially when strongly-typed - the combined interface forces users to write both mappings as a reciprocal pair. If mappings each way are in separate classes, users could write and unwittingly combine completely unrelated mappings? So for me, it feels less coherent to have them in separate classes? (unless I've misunderstood something)
@SeanFarrow Also meant to add: happy to do a skype call if that easier/quicker to work through some of this.
Possibly, when are you available, happy to be led by your time frames.
Cheers
@SeanFarrow Thx for the very productive skype call around caching. The CachePolicy architecture has now been rebased against the latest v5.0-alpha, as discussed, in this branch: https://github.com/App-vNext/Polly/tree/v5.x-cache-alpha
Community: We are initially planning targeting the following (pluggable) Cache providers for the CachePolicy:
MemoryCache or similar)Other cache providers you would like to see supported? Please comment on this issue, or join the conversation on slack: www.thepollyproject.org
@Reisenberger,
Thanks, I’ll start working on this, once the current backlog has cleared.
Cheers
Sean.
Other cache providers you would like to see supported?
@reisenberger , Gemfire (based on an apache project I think) could be nice. Maybe some gals from Steeltoe/Cloudfoundry could chime in? I am considering using that technology for distributed caching.
Hi,
Can you give us a pointer to the source code and I’ll take a look.
Cheers
Sean.
From: mfjerome [mailto:[email protected]]
Sent: Wednesday, December 07, 2016 19:42
To: App-vNext/Polly Polly@noreply.github.com
Cc: Sean Farrow sean.farrow@seanfarrow.co.uk; Mention mention@noreply.github.com
Subject: Re: [App-vNext/Polly] PROPOSAL: Cache policy (#136)
Other cache providers you would like to see supported?
@reisenbergerhttps://github.com/reisenberger , Gemfire (based on an apache project I think) could be nice. Maybe some gals from Steeltoe/Cloudfoundry could chime in? I am considering using that technology for distributed caching.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHubhttps://github.com/App-vNext/Polly/issues/136#issuecomment-265551251, or mute the threadhttps://github.com/notifications/unsubscribe-auth/ABY1flvH4aO11NqDuhpxl1yFcX2r8A6lks5rFwwMgaJpZM4JJmSo.
@SeanFarrow Hi Sean, excuse me for the delay.
Main website: https://pivotal.io/en/big-data/pivotal-gemfire
Documentation: http://gemfire.docs.pivotal.io/
C# Client API documentation: http://gemfire.docs.pivotal.io/docs-gemfire/gemfire_nativeclient/dotnet-caching-api/gemfire-csharp-dotnet-api.html
C# Client API code example: http://gemfire.docs.pivotal.io/docs-gemfire/gemfire_nativeclient/programming-examples/csharp-example.html
Hi,
All this is useful, but could you point me to the source and client source on GitHub?
Cheers
Sean.
From: mfjerome [mailto:[email protected]]
Sent: Wednesday, December 14, 2016 15:46
To: App-vNext/Polly Polly@noreply.github.com
Cc: Sean Farrow sean.farrow@seanfarrow.co.uk; Mention mention@noreply.github.com
Subject: Re: [App-vNext/Polly] PROPOSAL: Cache policy (#136)
@SeanFarrowhttps://github.com/SeanFarrow Hi Sean, excuse me for the delay.
· Main website: https://pivotal.io/en/big-data/pivotal-gemfire
· Documentation: http://gemfire.docs.pivotal.io/
· C# Client API documentation: http://gemfire.docs.pivotal.io/docs-gemfire/gemfire_nativeclient/dotnet-caching-api/gemfire-csharp-dotnet-api.html
· C# Client API code example: http://gemfire.docs.pivotal.io/docs-gemfire/gemfire_nativeclient/programming-examples/csharp-example.html
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHubhttps://github.com/App-vNext/Polly/issues/136#issuecomment-267068410, or mute the threadhttps://github.com/notifications/unsubscribe-auth/ABY1fg999w29AIzThA7Q-QPSOMa5E8jtks5rIA8ugaJpZM4JJmSo.
Hello,
I'm curious if you have a prediction of when this feature might land? It seems like there's been some promising work, but it's gone quiet recently. I have a strong interest in using the caching policy in combination with retry and circuit breaker for HTTP calls.
I'd also be happy to contribute if you need any help.
@perfectsquircle Yes, among all the other features that got delivered at v5.0, this got left behind. I've wanted to take forward, but it's been behind other things: contribution would be very welcome!
We have quite a developed architecture (thanks also to @SeanFarrow !), so the main thing we need now is some first cache provider implementations to plug into that. I've just re-based the architecture against latest Polly / stuff I'm about to release. Mini tour:
You construct a CachePolicy specifying:
CachePolicy. Various implementations already written..Execute(...)-ing on the policy. Users can write more elaborate strategies if they want (I will blog examples).So a typical cache might be configured something like:
Policy.Cache(
myCacheProvider,
TimeSpan.FromHours(1) // or more specific ITtlStrategy
/*, custom cache key strategy if desired */)
This test shows the basic usage.
So we need to implement some ICacheProviders. @perfectsquircle _Are you interested in in-process/local caching? (eg MemoryCache, disk cache), or more cloud-caching (eg Redis) or ...?_ Any contribution in any of these would be welcome! Even just an initial ICacheProvider implementation based on System.Runtime.Caching.MemoryCache, would be enough to launch the feature.
ICacheProvider implementations will often depend on third-party libraries, and we didn't want the main Polly package to take those dependencies, so each ICacheProvider would be delivered as a separate Nuget, built out of a separate github repo. The architecture also envisages support for serializers like Protobuf etc: let me know if you have any interest in that, and we can discuss further. Otherwise let's leave for now.
I am very available for further help / guidance, if you want to work on this! _Any of the above you'd be interested in tackling?_ (And: thank-you!)
@perfectsquirclehttps://github.com/perfectsquircle
I’m also like @reisenberger happy to help, although my time is limited currently.
If you are more interested in local caches I’m happy to write the cloud-based ones. Let us know how you wish to help and we’ll give you any support that is needed.
@reisenberger
Thank you for the comprehensive update. Maybe I'll get my feet wet and try to implement the memory or disk ICacheProvider. I suppose it would be sufficient to target .NET Standard 1.0 for these plugins?
@perfectsquircle Great!
I made a start on a skeleton Visual Studio solution, build file, Nuget Packager etc for MemoryCache repo early this morning. I can probably push that to github in about an hour's time ...
I suppose it would be sufficient to target .NET Standard 1.0 for these plugins?
As low a .Net Standard version as we can get away with. It looks from package search as if lowest .NET Standard for MemoryCache might be .NET Standard 1.3. Fine if that's the case. Although the core Polly targets .NetStandard 1.0 (soon to change to .NetStandard 1.1 when we release #231), it shouldn't be a problem to make MemoryCache repo target .NET Standard 1.3 instead. The range of cache providers we're targeting will inevitably mean some have differing target support - delivering them through separate nuget pkgs will let us deal with that.
@perfectsquircle At https://github.com/App-vNext/Polly.Caching.MemoryCache, there is now a repo ready to fork and develop on.
TL;DR All we need to do now is start developing the Polly.Caching.MemoryCache.MemoryCacheProvider : Polly.Caching.ICacheProvider within the Polly.Caching.MemoryCache.Shared area of this repo, and specs in SharedSpecs.
I put in a dummy class and test only to test the build script (build.bat) was working: can be deleted.
The repo intentionally keeps the three-target layout (.NET4.0, .NET4.5 and .Net Standard) that Polly has, for now. Theoretically we could drop .NET4.5 as a separate target and have .NET4.5 consumers reference .Net Standard, but targeting NetStandard from NetFramework is very noisy until Microsoft (hopefully) fix this in .Net Standard 2.0.
For MemoryCache, you may have to change the .Net Standard 1.0 package to target .Net Standard 1.3, if package search was accurate. (I left it at .Net Standard 1.0, so that this commit could be a useful master for other cache providers).
Finally, to reference the interface Polly.Caching.ICacheProvider, you'd need to be able to reference a Polly nuget which includes it. Which obviously isn't public yet. So the procedure would be clone
https://github.com/reisenberger/Polly/tree/v5.1.x-cache-rebase locally, run its build script, and reference the Polly nugets the build script places in the artifacts\nuget-package directory.
Phew - but that gets us a baseline to develop on!
_Let me know if makes sense / whatever questions - whether around tooling or_ MemoryCacheProvider _intent_.
Huge thank you for your contribution!
Hi @reisenberger,
I haven't gotten around to working on this. Things got crazy at work. I might try take another crack at it again soon.
Hi @reisenberger
Is there any chance you could setup a beta/alpha myget/vso feed based off the v5.1x-cache-rebase (if that's still the latest) branch?
Hi Joe,
What parts of caching are you specifically interested in?
Kind regards
Sean.
@joelhulen I have pulled the latest Cache rebase down onto this branch on App-vNext/Polly. Build from this branch will publish an appropriately tagged pre-release Polly build which you can push to nuget.
@JoeBrockhaus : @joelhulen plans to push the above to Nuget as a pre-release.
@JoeBrockhaus We would welcome contributions if you are able to contribute to Polly cache implementation - let us know what you would be interested in doing!
[ I can get back to CachePolicy myself likely in the second half of June. ]
@reisenberger @JoeBrockhaus Sorry, the notification for this thread got lost amongst my piles of emails. Sometimes it's faster getting ahold of me on the Polly slack channel ;-)
I'll work toward releasing the pre-release NuGet and notify everyone here once it's up.
@reisenberger @JoeBrockhaus I've published those pre-release NuGet packages. Please let me know if you have any issues finding or using them.
@SeanFarrow Sorry for the super-delay on this feedback.
I was looking to incorporate a combination of Retry with a CircuitBreaker to proactively serve from Cache before failing on new requests whose dependencies would likely fail, but for which cached data would suffice.
Don’t worry about the delay, what cache were you looking to use? Do you want sync or async?
Would likely be async, though i'm not sure if would be a blocker either way.
I have had to move onto other priorities in the meantime, unfortunately.
I'll try to find some time to poke it in the next couple days. 😀
Hi,
What specific cache(s) are you looking to use?
All,
I've just been looking at the memory cache, we can't provide an async api, as one does not exist. Does anyone see a problem with this?
Hi @SeanFarrow . Re:
I've just been looking at the memory cache, we can't provide an async api, as one does not exist. Does anyone see a problem with this?
I don't think this a significant problem. We can simply write an implementation for CacheAsync(...) that addresses a sync cache provider instead of an async one, at this line (and similar). It may mean a few extra configuration overloads, with the compiler selecting the right overload. We can add this when we next visit the cache architecture.
Is there an ETA on the caching feature?
@dweggemans The caching feature is expected to be released in September.
This branch https://github.com/App-vNext/Polly/tree/v5.3.x-cachebeta contains the latest caching architecture, ie the core classes within Polly to support CachePolicy. The build script will generate locally a nuget package for same.
This repo https://github.com/App-vNext/Polly.Caching.MemoryCache contains a beta-release of an ISyncCacheProvider and IAsyncCacheProvider implementation for MemoryCache. The build script will generate locally a beta nuget package for same. /cc @SeanFarrow
@dweggemans : Are there particular cache providers you are looking to support? Community contributions to support new cache providers will be welcome: The required interfaces to implement (ISyncCacheProvider and/or IAsyncCacheProvider) are relatively straightforward.
Polly contributors, eg @SeanFarrow , also already have a range of distributed cache providers in mind.
@reisenberger thanks for your response. I might be able to wait a little, or else I'll build a package locally. No problem.
The MemoryCache suits my needs perfectly. I'm just looking for a simple way to reduce some traffic by caching results locally.
Closing via #332
CachePolicy has been merged into the master branch, for release shortly as part of Polly v5.4.0.
The first cache provider implementation to go with CachePolicy - based around .NET's in-built MemoryCache - is available at: https://github.com/App-vNext/Polly.Caching.MemoryCache.
The two will be released together to nuget, as soon as we hook up the build and nuget feed onto https://github.com/App-vNext/Polly.Caching.MemoryCache. /cc @joelhulen