Хотя работа на сервере RavenDB обычно доставляет массу удовольствия, есть часть клиента RavenDB, которую я абсолютно ненавижу. Каждый раз, когда мне нужно прикоснуться к той части клиентского API, которая общается с сервером, это боль. Почему это?
Давайте рассмотрим простой пример, загружая документ по идентификатору. На проводе это выглядит так:
GET / docs / users / ayende
Это не может быть проще, за исключением того, что внутри RavenDB у нас есть три реализации для этого:
- Стандартная синхронизация
- Стандартный асинхронный импл
- Silverlight асинхронный импл
Проблема в том, что каждый из них использует разные API, и хотя мы создали общую абстракцию для async / sync, по крайней мере, очень трудно убедиться, что они все синхронизированы друг с другом, если нам нужно внести изменения.
Например, давайте посмотрим на три реализации 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; } }
Это API синхронизации, конечно, далее мы рассмотрим тот же метод для полной .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; } }); }
А вот версия Siliverlight:
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; } }); }
Я упоминал, что это раздражает?
Все эти методы делают точно то же самое, но я должен поддерживать 3 версии них. Я думал об отбрасывании версии синхронизации (синхронизировать поверх асинхронности легко), что означало бы, что у меня будет только 2 реализации, но мне это не нравится. Поддержка обработки ошибок и отладки для асинхронного все еще намного ниже того, что вы можете получить для кода синхронизации.
Я не знаю, есть ли решение для этого, я просто знаю, что это большая проблема для меня.
Источник: http://ayende.com/blog/59394/ravendb-implementation-frustrations