Ответ 1
У вас есть отличный пример маскировки команды. Приятно, что вы здесь делаете, что ваш метод обслуживания уже принимает один аргумент DoSomethingData
. Это ваше командное сообщение.
Здесь вам не хватает общей абстракции над обработчиками команд:
public interface ICommandHandler<TCommand>
{
void Handle(TCommand command);
}
С небольшим количеством рефакторинга ваш метод обслуживания будет выглядеть следующим образом:
// Vanilla dependency.
ICommandHandler<DoSomethingData> doSomethingHandler;
public void DoSomething(DoSomethingData data)
{
this.doSomethingHandler.Handle(data);
}
И, конечно, вам нужна реализация для ICommandHandler<DoSomethingData>
. В вашем случае это будет выглядеть так:
public class DoSomethingHandler : ICommandHandler<DoSomethingData>
{
public void Handle(DoSomethingData command)
{
// does the actual something
DoSomethingInternal(command);
}
}
Теперь вам может быть интересно, как насчет тех сквозных проблем, которые вы реализовали, таких как проверка аргументов, возможность пожара, обновление статуса канала публикации и обработка ошибок. Ну да, все они связаны с перекрестными проблемами, и ваш сервисный класс WCF И ваша бизнес-логика (DoSomethingHandler
) не должна беспокоиться об этом.
Существует несколько способов применения аспектно-ориентированного программирования. Некоторым нравится использовать инструменты для ткачества, такие как PostSharp. Недостатком этих инструментов является то, что они делают модульное тестирование намного сложнее, поскольку вы переплете все свои проблемы, связанные с перекрестными связями.
Второй способ - использовать перехват. Использование динамической генерации прокси и некоторого отражения. Тем не менее, вариации этого, которые мне нравятся больше, а это - применение декораторов. Приятная вещь в этом заключается в том, что это на мой взгляд самый чистый способ применения проблем с поперечной резкой.
Посмотрите на декоратор для проверки:
public class WcfValidationCommandHandlerDecorator<T> : ICommandHandler<T>
{
private IValidator<T> validator;
private ICommandHandler<T> wrapped;
public ValidationCommandHandlerDecorator(IValidator<T> validator,
ICommandHandler<T> wrapped)
{
this.validator = validator;
this.wrapped = wrapped;
}
public void Handle(T command)
{
if (!this.validator.ValidateArgument(command))
{
throw new FaultException(...);
}
// Command is valid. Let call the real handler.
this.wrapped.Handle(command);
}
}
Поскольку этот WcfValidationCommandHandlerDecorator<T>
является общим типом, мы можем обернуть его вокруг каждого обработчика команд. Например:
var handler = new WcfValidationCommandHandlerDecorator<DoSomethingData>(
new DoSomethingHandler(),
new DoSomethingValidator());
И вы можете легко создать декоратор, который обрабатывает любые заброшенные исключения:
public class WcfExceptionHandlerCommandHandlerDecorator<T> : ICommandHandler<T>
{
private ICommandHandler<T> wrapped;
public ValidationCommandHandlerDecorator(ICommandHandler<T> wrapped)
{
this.wrapped = wrapped;
}
public void Handle(T command)
{
try
{
// does the actual something
this.wrapped.Handle(command);
_publicationChannel.StatusUpdate(new Info
{
Status = transitionResult.NewState
});
}
catch (FaultException<MyError> faultException)
{
if (faultException.Detail.ErrorType == MyErrorTypes.EngineIsOffline)
{
TryFireEvent(MyServiceEvent.Error, faultException.Detail);
}
throw;
}
}
}
Вы видели, как я просто обернул ваш код в этом декораторе? Мы снова можем использовать этот декоратор для обертывания оригинала:
var handler =
new WcfValidationCommandHandlerDecorator<DoSomethingData>(
new WcfExceptionHandlerCommandHandlerDecorator<DoSomethingData>(
new DoSomethingHandler()),
new DoSomethingValidator());
Конечно, все это похоже на очень много кода, и если все, что у вас есть, это один единственный метод WCF, чем да, это, вероятно, слишком велико. Но это становится действительно интересным, если у вас есть дюжина или около того. Если у вас сотни? Ну.. Я не хочу, чтобы разработчик поддерживал эту базу кода, если вы не используете такую технику.
Итак, после нескольких минут рефакторинга вы получаете классы обслуживания WCF, которые просто зависят от интерфейсов ICommandHandler<TCommand>
. Все сквозные проблемы будут размещены в декораторах, и, конечно же, все будет объединено вашей библиотекой DI. Я думаю, вы знаете несколько: -)
Когда вы это сделаете, возможно, есть одна вещь, которую вы могли бы улучшить, потому что все ваши классы обслуживания WCF начнут выглядеть скучно одинаково:
// Vanilla dependency.
ICommandHandler<FooData> handler;
public void Foo(FooData data)
{
this.handler.Handle(data);
}
Придется начинать скучно писать новые команды и новые обработчики. У вас все еще будет поддержка службы WCF.
Вместо этого вы можете создать службу WCF с одним классом с помощью одного метода, например:
[ServiceKnownType("GetKnownTypes")]
public class CommandService
{
[OperationContract]
public void Execute(object command)
{
Type commandHandlerType = typeof(ICommandHandler<>)
.MakeGenericType(command.GetType());
dynamic commandHandler = Bootstrapper.GetInstance(commandHandlerType);
commandHandler.Handle((dynamic)command);
}
public static IEnumerable<Type> GetKnownTypes(ICustomAttributeProvider provider)
{
// create and return a list of all command types
// dynamically using reflection that this service
// must accept.
}
}
Теперь у вас есть служба WCF с единственным методом, который никогда не изменится. ServiceKnownTypeAttribute
указывает на GetKnownTypes
. WCF вызовет этот метод при запуске, чтобы узнать, какие типы он должен принять. Когда вы возвращаете список на основе метаданных приложения, он позволяет добавлять и удалять команды в систему, не меняя ни одной строки в вашей службе WCF.
Вы, вероятно, будете добавлять новые специальные декораторы WCF время от времени, и они обычно должны быть помещены в службу WCF. Другие декораторы, вероятно, будут более общими и могут быть помещены в бизнес-уровень. Они могут быть повторно использованы вашим приложением MVC, например.
Ваш вопрос был немного о CQRS, но мой ответ не имеет к этому никакого отношения. Ну... ничего не преувеличение. CQRS широко использует этот шаблон, но CQRS делает еще один шаг. CQRS - это совлокальные домены, которые заставляют вас запускать очереди и обрабатывать их асинхронно. Мой ответ, с другой стороны, касается применения принципов SOLID. SOLID хорошо повсюду. Не только в области совместной работы.
Если вы хотите узнать больше об этом, прочитайте мою статью о применении обработчиков команд. После этого продолжайте читать мою статью об использовании этого принципа для служб WCF. Мой ответ - резюме этих статей.
Удачи.