EDITH говорит (Tl; др)
Я пошел с вариантом предлагаемого решения; сохраняя все ICommandHandler
и IQueryHandler
s потенциально асинхронными и возвращающими разрешенную задачу в синхронных случаях. Тем не менее, я не хочу использовать Task.FromResult(...)
повсюду, так что я определил метод расширения для удобства:ICommandHandler/IQueryHandler с асинхронным/ждут
public static class TaskExtensions
{
public static Task<TResult> AsTaskResult<TResult>(this TResult result)
{
// Or TaskEx.FromResult if you're targeting .NET4.0
// with the Microsoft.BCL.Async package
return Task.FromResult(result);
}
}
// Usage in code ...
using TaskExtensions;
class MySynchronousQueryHandler : IQueryHandler<MyQuery, bool>
{
public Task<bool> Handle(MyQuery query)
{
return true.AsTaskResult();
}
}
class MyAsynchronousQueryHandler : IQueryHandler<MyQuery, bool>
{
public async Task<bool> Handle(MyQuery query)
{
return await this.callAWebserviceToReturnTheResult();
}
}
Жаль, что C# не Haskell ... пока 8-). Действительно пахнет приложением Arrows. В любом случае, надеюсь, что это поможет кому угодно. Теперь к моему первоначальному вопросу :-)
ВведениеЗдравствуйте!
Для проекта в настоящее время я разрабатываю архитектуру приложения на C# (.NET4.5, C# 5.0, ASP.NET MVC4). С этим вопросом я надеюсь получить некоторые мнения о некоторых проблемах, которые я наткнулся на попытку включить async/await
. Примечание: это довольно длительный один :-)
Моя структура решение выглядит следующим образом:
MyCompany.Contract
(команды/запросов и общих интерфейсов)MyCompany.MyProject
(содержит бизнес-логику и команду/запрос обработчики)MyCompany.MyProject.Web
(веб-интерфейс MVC)
Я читал на обслуживаемой архитектуре и Command-Query-Separa Тион и нашел эти сообщения очень полезные:
- Meanwhile on the query side of my architecture
- Meanwhile on the command side of my architecture
- Writing highly maintainable WCF services
До сих пор я получил мою голову вокруг ICommandHandler
/IQueryHandler
концепции и инъекции зависимостей от (я используя SimpleInjector - это действительно мертво просто).
Данный подход
подход из статей выше предлагает использовать Pocos в качестве команд/запросов и описывает диспетчеры из них в качестве реализации следующих интерфейсов обработчика:
interface IQueryHandler<TQuery, TResult>
{
TResult Handle(TQuery query);
}
interface ICommandHandler<TCommand>
{
void Handle(TCommand command);
}
В с MVC контроллер,» d использовать это следующим образом:
class AuthenticateCommand
{
// The token to use for authentication
public string Token { get; set; }
public string SomeResultingSessionId { get; set; }
}
class AuthenticateController : Controller
{
private readonly ICommandHandler<AuthenticateCommand> authenticateUser;
public AuthenticateController(ICommandHandler<AuthenticateCommand> authenticateUser)
{
// Injected via DI container
this.authenticateUser = authenticateUser;
}
public ActionResult Index(string externalToken)
{
var command = new AuthenticateCommand
{
Token = externalToken
};
this.authenticateUser.Handle(command);
var sessionId = command.SomeResultingSessionId;
// Do some fancy thing with our new found knowledge
}
}
Некоторые из моих наблюдений, касающихся этого подхода:
- В чистых CQS только запросы должны возвращать значения, пока команды должны быть, а только команды. В действительности удобнее для команд возвращать значения вместо выдачи команды, а затем выполнять запрос для того, что команда должна была вернуть в первую очередь (например, идентификаторы базы данных и т. П.).Вот почему the author suggested ставит возвращаемое значение в команду POCO.
- Не совсем очевидно, что возвращается из команды, на самом деле это выглядит как, как команда - это огонь и тип забытого типа, пока вы в конце концов не столкнетесь с нечетным результатом, которое будет доступно после выполнения обработчиком плюс команда теперь знает о его результатах
- У обработчиков есть, чтобы быть синхронными для этого, чтобы работать - запросы, а также команды. Как оказалось, с C# 5.0 вы можете вводить обработчики с питанием
async/await
с помощью вашего любимого контейнера DI, но компилятор не знает об этом во время компиляции, поэтому обработчик MVC терпит неудачу с исключением, указывая вам, что метод возвращается до завершения всех асинхронных задач.
Конечно, вы можете отметить обработчик MVC как async
, и об этом и говорит этот вопрос.
Команда Возвращающихся Значения
Я думал о данном подходе и внес изменения в интерфейсы для решения вопросов 1. и 2. в том, что я добавил ICommandHandler
, что имеет явный тип результата - так же, как IQueryHandler
. Это по-прежнему нарушает CQS, но по крайней мере, ясно видно, что эти команды возвращают какое-то значение с дополнительным преимуществом, не имея загромождать объект команды с результатом собственности:
interface ICommandHandler<TCommand, TResult>
{
TResult Handle(TCommand command);
}
Естественно можно утверждать, что, когда ты имеют тот же интерфейс для команд и запросов, зачем беспокоиться? Но я думаю, что стоит называть их по-другому - просто выглядит чище для моих глаз.
Мои Предварительное решение
Потом я долго думал о 3-м выпуске под рукой ... некоторые из моих обработчиков команд/запроса должны быть асинхронными (например, выдача WebRequest
на другой веб-сервис для аутентификации) другие Дон» т. Так что я решил, что было бы лучше, чтобы разработать свои обработчики с нуля для async/await
- что, конечно, пузыри до MVC обработчиков даже для обработчиков, которые фактически синхронно:
interface IQueryHandler<TQuery, TResult>
{
Task<TResult> Handle(TQuery query);
}
interface ICommandHandler<TCommand>
{
Task Handle(TCommand command);
}
interface ICommandHandler<TCommand, TResult>
{
Task<TResult> Handle(TCommand command);
}
class AuthenticateCommand
{
// The token to use for authentication
public string Token { get; set; }
// No more return properties ...
}
AuthenticateController:
class AuthenticateController : Controller
{
private readonly ICommandHandler<AuthenticateCommand, string> authenticateUser;
public AuthenticateController(ICommandHandler<AuthenticateCommand,
string> authenticateUser)
{
// Injected via DI container
this.authenticateUser = authenticateUser;
}
public async Task<ActionResult> Index(string externalToken)
{
var command = new AuthenticateCommand
{
Token = externalToken
};
// It's pretty obvious that the command handler returns something
var sessionId = await this.authenticateUser.Handle(command);
// Do some fancy thing with our new found knowledge
}
}
Хотя это решает мои проблемы - очевидные возвращаемые значения, все обработчики могут быть асинхронными - мне больно, чтобы мой мозг поставил async
на вещь, которая не асинхронна только потому, что. Есть несколько недостатков я вижу с этим:
- интерфейсы обработчика не так аккуратно, как я хотел, чтобы они - в
Task<...>
thingys в моих глазах очень многословным и на первый взгляд затемнять тот факт, что я хочу только возвратите что-нибудь из запроса/команды - Компилятор предупреждает вас о том, что у вас нет подходящего
await
в реализациях синхронного обработчика (я хочу, чтобы иметь возможность компилировать мойRelease
сWarnings as Errors
) - вы можете перезаписать это с помощью прагмы ... да. .. Что ж ... - Я мог бы опустить
async
ключевое слово в этих случаях, чтобы сделать компилятор счастливым, но для того, чтобы реализовать интерфейс обработчика вы должны вернуть какие-тоTask
в явном виде - это довольно некрасиво - я мог бы поставить синхронные и асинхронные версии интерфейсы обработчика (или помещают все их в один интерфейс, раздувая реализацию), но я понимаю, что в идеале потребитель обработчика не должен знать о том, что обработчик команды/запроса синхронизирован или асинхронен, поскольку это перекрестная озабоченность. Что делать, если мне нужно сделать ранее синхронную команду async? Я должен был бы изменить каждого потребителя обработчика, потенциально нарушающего семантику, на моем пути через код.
- С другой стороны, потенциально -async-обработчики-подход мог бы даже дать мне возможность изменить синхронизации обработчиков быть асинхронной, украшая их с помощью моей DI контейнера
Прямо сейчас я не Лучшее решение ... Я в недоумении.
Любой, у кого есть аналогичная проблема и изящное решение, о котором я не думал?
Спасибо, понятия на самом деле очень похожи ... Я добавил свое личное решение вопроса для удобства. – mfeineis