gpt4 book ai didi

c# - 重构 “procedural” WCF服务

转载 作者:IT王子 更新时间:2023-10-29 04:33:16 24 4
gpt4 key购买 nike

我正在尝试将可怕的WCF服务重构为更易于管理的东西。
在撰写本文时,该服务通过构造函数获取约9个依赖项,这使得单元测试非常困难。

该服务正在通过状态机处理本地状态,对参数进行验证,引发故障异常,执行实际操作并通过pub/sub channel 触发发布事件。在所有其他服务调用中,此代码非常相似。

我意识到我可以通过Aspect-Oriented Programming或WCF行为来不同地执行其中的一些操作(参数验证,发布/订阅通知),但是我的直觉告诉我,一般方法是错误的-感觉太“程序化”了。

我的目标是将实际操作的执行与发布/订阅通知,甚至错误处理之类的分开。

我想知道DDDCQRS之类的首字母缩写词或其他技术是否可以在这里提供帮助?不幸的是,我对定义之外的那些概念不是很熟悉。

这是一个此类WCF操作的(简化)示例:

public void DoSomething(DoSomethingData data)
{
if (!_stateMachine.CanFire(MyEvents.StartProcessing))
{
throw new FaultException(...);
}

if (!ValidateArgument(data))
{
throw new FaultException(...);
}

var transitionResult =
_stateMachine.Fire(MyEvents.StartProcessing);

if (!transitionResult.Accepted)
{
throw new FaultException(...);
}

try
{
// does the actual something
DoSomethingInternal(data);

_publicationChannel.StatusUpdate(new Info
{
Status = transitionResult.NewState
});
}
catch (FaultException<MyError> faultException)
{
if (faultException.Detail.ErrorType ==
MyErrorTypes.EngineIsOffline)
{
TryFireEvent(MyServiceEvent.Error,
faultException.Detail);
}
throw;
}
}

最佳答案

您所拥有的就是一个很好的变相命令示例。您在这里所做的事情很高兴,您的服务方法已经接受了一个参数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);
}
}

现在您可能想知道,您实现的那些跨 Realm 关注点如何处理,例如参数验证, jar 头触发,发布 channel 状态更新和错误处理。是的,它们都是跨 Realm 的问题,您的WCF服务类和业务逻辑( DoSomethingHandler)都不必为此担心。

有几种方法可以应用面向方面的编程。有些人喜欢使用代码编织工具,例如PostSharp。这些工具的缺点是,由于您将所有交叉问题都纳入其中,因此它们使单元测试变得更加困难。

第二种方法是使用拦截。使用动态代理生成和一些反射(reflection)。但是,我更喜欢这种变化,那就是应用装饰器。这样做的好处是,根据我的经验,这是应用横切关注点的最简洁方法。

让我们看一下装饰器以进行验证:

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's 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服务方法而不是,那么这可能就太过分了。但是,如果有十几个左右,它就会变得非常有趣。如果你有几百个?好吧..如果您不使用这种技术,我不想成为维护该代码库的开发人员。

因此,经过几分钟的重构,您最终会得到仅依赖于 ICommandHandler<TCommand>接口(interface)的WCF服务类。所有横切关注点都将放置在装饰器中,并且当然所有内容都由您的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在任何地方都很好。不仅在协作 Realm 。

如果您想了解更多有关此的内容,请阅读我有关应用 command handlers的文章。之后,继续阅读 my article about applying this principle to WCF services。我的回答是这些文章的摘要。

祝你好运。

关于c# - 重构 “procedural” WCF服务,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14829818/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com