RavenDB implementation frustrations
Join the DZone community and get the full member experience.
Join For FreeLet us take a the simple example, loading a document by id. On the wire, it looks like this:
GET /docs/users/ayende
It can’t be simpler than that, except that internally in RavenDB, we have three implementation for that:
- Standard sync impl
- Standard async impl
- Silverlight async impl
The
problem is that each of those uses different API, and while we created a
shared abstraction for async / sync, at least, it is a hard task making
sure that they are all in sync with one another if we need to make a
modification.
For example, let us look at the three implementation of Get:
public JsonDocument DirectGet(string serverUrl, string key) { var metadata = new RavenJObject(); AddTransactionInformation(metadata); var request = jsonRequestFactory.CreateHttpJsonRequest(this, serverUrl + "/docs/" + key, "GET", metadata, credentials, convention); request.AddOperationHeaders(OperationsHeaders); try { var requestString = request.ReadResponseString(); RavenJObject meta = null; RavenJObject jsonData = null; try { jsonData = RavenJObject.Parse(requestString); meta = request.ResponseHeaders.FilterHeaders(isServerDocument: false); } catch (JsonReaderException jre) { var headers = ""; foreach (string header in request.ResponseHeaders) { headers = headers + string.Format("\n\r{0}:{1}", header, request.ResponseHeaders[header]); } throw new JsonReaderException("Invalid Json Response: \n\rHeaders:\n\r" + headers + "\n\rBody:" + requestString, jre); } return new JsonDocument { DataAsJson = jsonData, NonAuthoritiveInformation = request.ResponseStatusCode == HttpStatusCode.NonAuthoritativeInformation, Key = key, Etag = new Guid(request.ResponseHeaders["ETag"]), LastModified = DateTime.ParseExact(request.ResponseHeaders["Last-Modified"], "r", CultureInfo.InvariantCulture).ToLocalTime(), Metadata = meta }; } catch (WebException e) { var httpWebResponse = e.Response as HttpWebResponse; if (httpWebResponse == null) throw; if (httpWebResponse.StatusCode == HttpStatusCode.NotFound) return null; if (httpWebResponse.StatusCode == HttpStatusCode.Conflict) { var conflicts = new StreamReader(httpWebResponse.GetResponseStreamWithHttpDecompression()); var conflictsDoc = RavenJObject.Load(new JsonTextReader(conflicts)); var conflictIds = conflictsDoc.Value<RavenJArray>("Conflicts").Select(x => x.Value<string>()).ToArray(); throw new ConflictException("Conflict detected on " + key + ", conflict must be resolved before the document will be accessible") { ConflictedVersionIds = conflictIds }; } throw; } }
This is the sync API, of course, next we will look at the same method, for the full .NET framework TPL:
public Task<JsonDocument> GetAsync(string key) { EnsureIsNotNullOrEmpty(key, "key"); var metadata = new RavenJObject(); AddTransactionInformation(metadata); var request = jsonRequestFactory.CreateHttpJsonRequest(this, url + "/docs/" + key, "GET", metadata, credentials, convention); return Task.Factory.FromAsync<string>(request.BeginReadResponseString, request.EndReadResponseString, null) .ContinueWith(task => { try { var responseString = task.Result; return new JsonDocument { DataAsJson = RavenJObject.Parse(responseString), NonAuthoritiveInformation = request.ResponseStatusCode == HttpStatusCode.NonAuthoritativeInformation, Key = key, LastModified = DateTime.ParseExact(request.ResponseHeaders["Last-Modified"], "r", CultureInfo.InvariantCulture).ToLocalTime(), Etag = new Guid(request.ResponseHeaders["ETag"]), Metadata = request.ResponseHeaders.FilterHeaders(isServerDocument: false) }; } catch (WebException e) { var httpWebResponse = e.Response as HttpWebResponse; if (httpWebResponse == null) throw; if (httpWebResponse.StatusCode == HttpStatusCode.NotFound) return null; if (httpWebResponse.StatusCode == HttpStatusCode.Conflict) { var conflicts = new StreamReader(httpWebResponse.GetResponseStreamWithHttpDecompression()); var conflictsDoc = RavenJObject.Load(new JsonTextReader(conflicts)); var conflictIds = conflictsDoc.Value<RavenJArray>("Conflicts").Select(x => x.Value<string>()).ToArray(); throw new ConflictException("Conflict detected on " + key + ", conflict must be resolved before the document will be accessible") { ConflictedVersionIds = conflictIds }; } throw; } }); }
And here is the Siliverlight version:
public Task<JsonDocument> GetAsync(string key) { EnsureIsNotNullOrEmpty(key, "key"); key = key.Replace("\\",@"/"); //NOTE: the present of \ causes the SL networking stack to barf, even though the Uri seemingly makes this translation itself var request = url.Docs(key) .ToJsonRequest(this, credentials, convention); return request .ReadResponseStringAsync() .ContinueWith(task => { try { var responseString = task.Result; return new JsonDocument { DataAsJson = RavenJObject.Parse(responseString), NonAuthoritiveInformation = request.ResponseStatusCode == HttpStatusCode.NonAuthoritativeInformation, Key = key, LastModified = DateTime.ParseExact(request.ResponseHeaders["Last-Modified"].First(), "r", CultureInfo.InvariantCulture).ToLocalTime(), Etag = new Guid(request.ResponseHeaders["ETag"].First()), Metadata = request.ResponseHeaders.FilterHeaders(isServerDocument: false) }; } catch (AggregateException e) { var webException = e.ExtractSingleInnerException() as WebException; if (webException != null) { if (HandleWebExceptionForGetAsync(key, webException)) return null; } throw; } catch (WebException e) { if (HandleWebExceptionForGetAsync(key, e)) return null; throw; } }); }
Did I mention it is annoying?
All of those methods are doing the exact same thing, but I have to maintain 3 versions of them. I thought about dropping the sync version (it is easy to do sync on top of async), which would mean that I would have only 2 implementations, but I don’t like it. The error handling and debugging support for async is still way below what you can get for sync code.
I don’t know if there is a solution for this, I just know that this is a big pain point for me.
Source: http://ayende.com/blog/59394/ravendb-implementation-frustrations
Opinions expressed by DZone contributors are their own.
Comments