As you may or may not know, Agatha has supported server-side caching of responses for a while now (it also sports built-in client-side caching actually). But it only came with one in-memory implementation of that cache. And while that implementation works well, it's still just an in-process cache which just isn't sufficient for some scenarios.
This week I was introduced to Membase, a great distributed caching solution which is very easy to set up. I wanted to see what it would take to make Agatha's server-side caching work with Membase. With a little help from the Enyim Membase client, it turned out to be very easy. If you want to change the actual caching implementation that Agatha uses, you have to implement 2 interfaces. First, you'll need a custom implementation of the ICache interface:
With Agatha's caching, you can use regions in your caching configuration. A region corresponds with a bucket in Membase. If you don't specify a region name when configuring caching for a response, Agatha will use the default region which is named defaultRegion. You will need to create at least the defaultRegion bucket in your Membase cluster, and you'll also need to create a bucket for each other region you use in your caching configuration. When your service layer is initialized, Agatha will create an ICache instance for each known region to be used.
Then you'll need an ICacheProvider implementation:
Now, because the Enyim Membase client uses binary serialization of cached objects by default, we're going to provide our own ITranscoder (defined in the Enyim assembly) implementation which uses the DataContractSerializer:
That's actually all we need to support distributed response caching with Membase. To use this, you'd need to add this to the configuration file of your service host:
Obviously, you'd need a bucket definition for every region that you'd use and you'll probably need a different bucket URI as well ;)
You'd also need to configure Agatha to use the MembaseCacheProvider implemention:
And that's it... distributed caching of service-layer responses has never been this easy ;)
Note that I haven't committed this implementation to Agatha's Subversion repository... the plan is to add it in the 2.0 version, which will have many more changes (more on that in a future post). But if you need it already, or you need inspiration for an implementation that targets a different distributed caching server, the information in this post should get you going.
Written by Davy Brion, published on 2011-02-27 19:33:45
Soon after I introduced Agatha's server-side caching, people started asking for client-side caching as well. There are indeed quite a few scenarios where it makes sense to cache the response of a service call on the client instead of merely doing it on the server. After all, what kind of service call can be faster than one you don't have to make? It's finally been added to the trunk, and it will be available in the upcoming 1.2 release. So for those of you want to use it but are still on 1.1, you can safely upgrade to the current trunk.
Usage of the client-side caching is practically identical to usage of the server-side caching feature, which means you have to use the EnableClientResponseCaching attribute on your request types where you want to use caching, and they have to implement the Equals and GetHashCode methods properly (read this post for more info on this).
Here's a small example from the Sample that is included in the Agatha source code:
In this case, both the EnableServiceResponseCaching and EnableClientResponseCaching attributes were used, which means that responses for this request will be cached for 5 minutes on the server, and for 30 seconds on the client. You don't have to use both attributes simultaneously, you can use either one of them as well depending on where you want to cache the response. You can also set a region (like you can with the server-side caching) so you can explicitely clean a certain region of the cache if you need to at some point.
Everything happens transparently from your code. If you make a service call with one request, and there is a cached response available for that request, the service call is not made and you'll get the cached response. If you send a batch of requests, and there are cached responses available for any of those requests, the call that is sent to the service will only include the requests for which no cached response was available.
Written by Davy Brion, published on 2010-08-09 16:27:51
One of the most important new additions in the Agatha 1.1 release is the ability to have the service layer cache responses for requests that are eligible for caching. Obviously, this doesn’t happen automatically and you need to configure this yourself. Unfortunately, I've never really written a post to describe how to do this and what you need to keep in mind. Hopefully, all of this will be clear after this post.
There are only two things you need to do to use Agatha’s server-side caching feature:
Use the EnableServiceResponseCaching attribute
If you want certain Request-derived types to be eligible for caching, you need to put the EnableServiceResponseCaching attribute on top of them. This attribute enables you to set the logical region (more on that later) where the Response for this Request needs to stored in the cache, and it requires you to set an expiration. Here’s a simple example:
For this particular request type, responses should be cached for a maximum duration of 10 minutes, and the cached responses will be stored in the Issues region. A region is pretty much just a section within the cache. If you don’t specify a region, each response will be placed in the default region. If you do specify one, each cached response for that region is placed within that section. This gives you the ability to clear an entire region (and thus, all the cached responses that are stored in that region) without impacting any of the other regions (including the default one).
The expiration can be configured by providing a number of hours, minutes or seconds (or a combination of those three) to the attribute.
Now obviously, Agatha needs a way to differentiate between multiple instances of the GetUnassignedIssuesForProjectRequest class. More specifically, Agatha needs to know when a request can be considered equal to a previous request for which a response has already been cached. So that brings us to the next thing you need to do:
Override the Equals and GetHashCode methods
The response for a GetUnassignedIssuesForProjectRequest instance with ProjectId set to e35c60f7-c35e-43db-9988-0dab3f39c61b will obviously contain a different set of unassigned issues than one for a GetUnassignedIssuesForProjectRequest with ProjectId set to 5d47161a-f334-4cce-9cc4-9606a9d294a6. To make sure that Agatha knows which response can be returned for a given request, your request needs to override the Equals and GetHashCode methods so you can tell Agatha when an instance of a certain request can be considered equal to one for which a cached response already exists. In the case of our example, requests of type GetUnassignedIssuesForProjectRequest can be considered equal if they both return the same value through their ProjectId property. So in this case, our request class needs to look like this:
Now that we have an Equals and a GetHashCode method which only looks at the value of the ProjectId property, Agatha can differentiate between different requests on a value basis instead of a reference basis. Note that this doesn’t mean that your request types need to be value objects in the truest sense of the term. They just need to be able to perform an equality check based on the values that make the difference between actually handling the request, or returning the response that has previously been cached for the set of values that you’re using to determine equality. Simply overriding the Equals method is not enough, since Agatha will use these instances of requests as keys in a dictionary so you need to provide a proper GetHashCode implementation as well (which is recommended anyway if you’re overriding the Equals method).
It’s very important to really consider which properties you want to include in the equality check and the hashcode calculation. If your request type inherits from some base request (typically one that contains user credentials and stuff like that), then you typically don’t want to include those inherited property values in your equality check, unless you really want to cache different responses based on one of those inherited properties. I'd recommend writing enough tests to verify that your equality checks and hashcode calculations indeed behave the way you want them to because if they don’t, you will either get suboptimal results from Agatha’s caching or even incorrect ones which would lead to bugs that will be very hard to debug.
A question that came up recently in the Agatha discussion group was how to implement the Equals and GetHashCode method for request types for which each instance should really be considered equal. For instance, a request type like this:
In this case, the default Equals and GetHashCode implementations will be reference-based and not value based. But as you can see, there are no values to differentiate between requests. In this case, there is only one way to retrieve the known Countries in this system, so how do we implement the Equals and GetHashCode methods so this request type can be used correctly with Agatha’s caching layer? Well, the solution isn’t very nice but it is pretty simple. You can just introduce a dummy field with a fixed value:
Now, every instance of the GetAllCountriesRequest will be considered equal to each other and they’ll all return the same hashcode as well. So every incoming instance of this request will return the same cached response (once it’s been cached that is).
That’s pretty much it. In itself, the caching layer of Agatha is very easy to use, but you definitely need to make sure that your Equals and GetHashCode implementations are correct. That’s pretty much the only tricky part (and downside) to how Agatha’s caching works, but I was unfortunately unable to come up with something that was easier to use.
One final word on the usage of regions. If you’re caching responses, then you typically want a way to remove stale responses from the cache. If some of the data that you’re caching is changed before the cached responses expire, you can clear the region in which those cached responses are being stored. Just add an ICacheManager constructor parameter to your handler (or any other class for that matter) and call the Clear method which takes a region name as a parameter.
As always with caching: be careful in how you use it, and make sure you think it through ;)
Written by Davy Brion, published on 2010-06-20 17:00:46
A question that also comes up occasionally is how you can use an Agatha service from a client which isn’t aware of Agatha? Or more specifically: can an Agatha service be used from a client which has generated a proxy based on the WSDL of the Agatha service? The answer is yes!
First of all, make sure your service exposes its metadata. You do this in the usual WCF fashion:
The serviceMetadata element with the httpGetEnabled=”true” attribute is the important one in the snippet above.
After that, you can simply generate a service proxy through visual studio or svcutil or whatever:
Now you can write the following code to communicate with your Agatha Service Layer:
Notice that there are no Agatha-related using statements, nor is there any reference to Agatha or the assembly which contains the Request/Response types. All of the required data can be found within the WSDL and you can generate proxies for it just as you could with any other WCF service. The client-side usage model is of course as bad as it always is with standard WCF (for more information, be sure to read: Why I Dislike Classic Or Typical WCF Usage) but if you’re willing to put up with it, then at least you can ;)
This also means that you should be able to generate service proxies for other platforms as well, as long as they support SOAP services.
Written by Davy Brion, published on 2010-05-14 14:49:35
One question that occasionally comes up is how a client can use multiple Agatha services. While it’s not possible with Agatha’s out-of-the-box configuration and usage patterns, it can be done if you’re willing to write a little bit of glue code. So, let’s go through the steps to make it work. I'm gonna use one of our Silverlight applications for this example, but it works just the same for regular .NET clients.
First of all, the Silverlight client’s WCF config has 2 defined endpoints:
Notice that each endpoint has a defined name.
Now you can create 2 new service proxies which inherit from Agatha’s proxy:
Once you have those proxies, you can create new request dispatchers, which also inherit from Agatha’s:
If you register those types with your container (make sure it’s the same container instance that Agatha uses) and you can have the correct dispatchers injected for whichever service you want to communicate with. But in the case of asynchronous request dispatchers, you typically use a factory to get them, so you probably want to add something like this:
And there you go… inject the proper factory (or both of them) and just use whichever one you need to get a dispatcher for the service you want.
Written by Davy Brion, published on 2010-05-08 18:21:35