Хотя работа на сервере 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