解决调用链反模式

发布于 2024-10-25 05:05:54 字数 715 浏览 0 评论 0原文

我开始注意到 ASP.NET 开发中存在一些反模式。这让我很困扰,因为感觉这是保持良好设计的正确做法,但同时又闻起来不对劲。

问题是这样的:我们有一个多层应用程序,底层是一个处理对为我们提供数据的服务的调用的类。其之上是一层可以转换、操作和检查数据的类。其上方是 ASP.NET 页面。

在许多情况下,服务层的方法在进入视图之前不需要任何更改,因此模型只是直接传递,例如:

public List<IData> GetData(int id, string filter, bool check)
{
    return DataService.GetData(id, filter, check);
}

这并没有错,也不一定很糟糕,但它创建了一个奇怪的复制/粘贴依赖性。我也在做底层服务,它也大量复制了这个模式,而且到处都有接口。所以发生的情况是,“我需要将 int someotherID 添加到 GetData”,因此我将其添加到模型、服务调用者、服务本身和接口中。 GetData 实际上代表了多个使用相同签名但返回不同信息的方法,这并没有什么帮助。界面对这种重复有所帮助,但它仍然时不时地出现。

这种反模式有名字吗?是否有解决办法,或者对架构进行重大更改是唯一真正的方法?听起来我需要扁平化我的对象模型,但有时数据层正在进行转换,因此它有价值。我还喜欢将代码“调用外部服务”和“提供页面数据”分开。

I've begun to notice something of an anti-pattern in my ASP.NET development. It bothers me because it feels like the right thing to do to maintain good design, but at the same time it smells wrong.

The problem is this: we have a multi-layered application, the bottom layer is a class handling calls to a service that provides us with data. Above that is a layer of classes that possible transform, manipulate, and check the data. Above that are the ASP.NET pages.

In many cases, the methods from the the service layer don't need any changes before going on the view, so the model is just a straight pass through, like:

public List<IData> GetData(int id, string filter, bool check)
{
    return DataService.GetData(id, filter, check);
}

It's not wrong, nor necessarily awful to work on, but it creates an odd kind of copy/paste dependency. I'm also working on the underlying service, and it also replicates this patter a lot, and there are interfaces throughout. So what happens is, "I need to add int someotherID to GetData" So I add it to the model, the service caller, the service itself, and the interfaces. It doesn't help that GetData is actually representative of several methods that all use the same signature but return different information. The interfaces help a bit with that repetition, but it still crops up here and there.

Is there a name for this anti-pattern? Is there a fix, or is a major change to the architecture the only real way? It sounds like I need to flatten my object model, but sometimes the data layer is doing transformations so it has value. I also like keeping my code separated between "calls an outside service" and "supplies page data."

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

完美的未来在梦里 2024-11-01 05:05:59

必须定义什么样的责任属于哪一层,并且仅将此类逻辑放置在其所属的层中。

如果不需要在特定方法中添加任何逻辑,那么直接通过是绝对正常的。有时您可能需要这样做,此时抽象层就会得到回报。

拥有并行层次结构甚至更好,而不仅仅是向上传递底层的对象,因此每个层都使用它自己的类层次结构,如果您觉得层次结构没有太大差异,您可以使用 AutoMapper 之类的东西。这为您提供了灵活性,并且您始终可以在特定方法/类中用自定义映射代码替换自动映射,以防层次结构不再匹配。

如果许多方法具有几乎相同的签名,那么您应该考虑查询规范模式。

IData GetData(IQuery<IData> query)

然后,在表示层中,您可以为自定义查询规范对象实现一个数据绑定器,其中单个 aspnet 处理程序可以实现特定查询对象的创建,并将它们传递给单个服务方法,该方法将其传递给单个存储库方法,其中它可以根据特定的查询类(可能使用访问者模式)进行分派。

IQuery<IData> BindRequest(IHttpRequest request)

通过这种自动映射和查询规范模式,您可以将重复减少到最低限度。

It's essential to define what kind of responsibility goes to which layer, and place such logic only in the layer it belongs to.

It's absolutely normal to just pass through, if you don't have to add any logic in particular method. At some time you might need to do so, and abstraction layer will pay off at that point.

It's even better to have parallel hierarchies, not just passing the underlying layer's objects up, so each layer uses it's own class hierarchy, and you can employ something like AutoMapper in case you feel there's no much difference in the hierarches. This gives you flexibility, and you can always replace automapping with custom mapping code in particular methods/classes, in case hierarchies do not match anymore.

If you many methods with almost the same signature, then you should think of Query Specification pattern.

IData GetData(IQuery<IData> query)

Then, in presentation layer you can implement a databinder for your custom query specification objects, where a single aspnet handler could implement creation of specific query objects, and passing them to a single service method, which will pass it to a single repository method, where it can be dispatched according to a specific query class, possibly with a Visitor pattern.

IQuery<IData> BindRequest(IHttpRequest request)

With this to Automapping and Query Specification pattern, you can reduce duplication to a minimum.

为你拒绝所有暧昧 2024-11-01 05:05:57

其实,你选择走的路,就是你拥有你所拥有的东西的原因(我并不是说它不好)。

首先我想说的是,你的做法很正常。

现在,让我思考一下您的层:

  • 您的服务 - 提供某种强类型的访问模型。这意味着它有一些类型的参数,在一些特殊类型的方法中使用它们,这些方法再次返回一些特殊类型的结果。
  • 您的服务访问层 - 也提供相同类型的模型。因此,它需要特殊类型的方法的特殊类型的参数,返回特殊类型的结果。
  • 等等...

为了不混淆,这就是我所说的特殊类型

public UserEntity GetUserByID(int userEntityID);

在此示例中,您需要准确传递 Int,同时准确调用 GetUserByID,它将准确返回 UserEntity 对象。

现在另一种方法

还记得SqlDataReader 是如何工作的吗?不是很强类型,对吧?
在我看来,您在这里呼吁的是您缺少一些非强类型层。

为此:您需要在层中的某个位置从强类型切换到非强类型。

示例

public Entity SelectByID(IEntityID id);
public Entity SelectAll();

因此,如果您有这样的东西而不是服务访问层,那么您可以为您想要的任何参数调用它。

但是,这几乎是在创建您自己的 ORM,所以我认为这不是最好的方法。

Actually, the way you have chosen to go, is the reason of having what you have (I am not saying it is bad).

First, let me say your approach is quite normal.

Now, let me go thought your layers:

  • Your service - provides somewhat kind of strongly-typed access model. What that means is it has some types of arguments, used them in some special types of methods which return again some special type of results.
  • Your service-access-layer - also provides the same kind of model. So that it takes special kinds of arguments for special kinds of methods, returning special kinds of results.
  • etc...

In order not to confuse, here is what I call special kind:

public UserEntity GetUserByID(int userEntityID);

In this example you need to pass exactly the Int, while calling exactly the GetUserByID and it will return exactly the UserEntity object.

Now another kind of approach:

Remember how SqlDataReader works? not very strongly-typed, right?
What you call here for, in my opinion, is that you are missing some not-strongly typed layer.

For that to happen: you need to switch from strongly-typed to non-strongly typed somewhere in your layers.

Example:

public Entity SelectByID(IEntityID id);
public Entity SelectAll();

So, if you had something like this instead of the service access layer, then you could call it for whichever arguments you wanted.

But, that is almost creating an ORM of your own, so I would not think this is the best way to go.

荆棘i 2024-11-01 05:05:56

我也使用这种模式。然而,我使用它的目的是将我的域模型对象与我的数据对象解耦。

就我而言,我没有像示例中那样“传递”来自数据层的对象,而是将其“映射”到域层中的另一个对象。我使用 AutoMapper 来消除手动执行此操作的痛苦。

在大多数情况下,我的域对象看起来与它源自的数据对象完全相同。然而,有时我需要展平来自数据对象的信息...或者我可能对数据对象中的所有内容不感兴趣等等。我将数据对象映射到仅包含字段的自定义域对象我的域层感兴趣。

此外,这还有一个副作用,当我决定重构或更改我的数据层以进行其他操作时,它不必影响我的域对象,因为它们是使用映射技术解耦的。

以下是自动映射器的描述,我认为这就是此设计模式试图实现的目标:

AutoMapper面向模型投影场景,将复杂的对象模型扁平化为DTO和其他简单对象,其设计更适合序列化、通信、消息传递,或者只是域和应用层之间的反腐败层

I use this pattern as well. However I used it for the purpose of de-coupling my domain model objects from my data objects.

In my case, instead of "passing through" the object coming from the data layer as you do in your example, I "map" it to another object that lives in my domain layer. I use AutoMapper to take out the pain of manually doing it.

In most cases my domain object looks exactly the same as my data object that it originated from. However there are times when I need to flatten information coming from my data object... or I may not be interested in everything that is in my data object etc.. I map the data object to a customized domain object that only holds the fields my domain layer is interested in.

Also this has the side effect that when I decide to re factor or change my data-layer for something else, It does not have to affect my domain objects since they are de-coupled using the mapping technique.

Here is a description of auto-mapper, which is sort of what this design pattern tries to achieve I think:

AutoMapper is geared towards model projection scenarios to flatten complex object models to DTOs and other simple objects, whose design is better suited for serialization, communication, messaging, or simply an anti-corruption layer between the domain and application layer

风蛊 2024-11-01 05:05:55

在我看来,您需要另一个接口,以便该方法变得类似于:

public List<IData> GetData(IDataRequest request)

Sounds to me like you need another interface, so that the method becomes something like:

public List<IData> GetData(IDataRequest request)
月亮是我掰弯的 2024-11-01 05:05:55

你将权力委托给另一层,这并不一定是一件坏事。

您可以在此处或在其他方法中添加一些其他逻辑,这些逻辑仅属于该层,或者将层委托给另一个实现,因此它肯定可以很好地利用所讨论的层。

你可能有太多的层次,但我不会仅仅因为看到这个而这么说,更多的是因为没有看到其他任何东西。

You're delegating to another layer, and it's not necessarily a bad thing at all.

You could add some other logic here or in another method down the line, that belongs only in this layer, or swap out to having the layer delegated-to with another implementation, so it certainly could be perfectly good use of the layers in question.

You may have too many layers, but I wouldn't say so just from seeing this, more from not seeing anything else.

人疚 2024-11-01 05:05:55

根据您的描述,听起来您似乎在应用程序中遇到了抽象的“权衡”之一。

考虑这些“调用链”不再“传递”数据但需要进行一些转换的情况。现在可能不需要,当然可以为 YAGNI 提供这种情况。

然而,在这种情况下,似乎并没有太多的技术债务来处理能够轻松地在层之间引入数据更改的积极副作用。

From what you've described it simply sounds like you have encountered one of the 'trade-offs' of abstraction in your application.

Consider the case where those 'call-chains' no longer 'pass-thru' the data but require some tranformation. It might not be needed now and certainly the case can be made for YAGNI.

However, in this case it doesn't seem like too much tech debt to handle with the positive side effect of being able to easily introduce changes to the data between layers.

水水月牙 2024-11-01 05:05:54

我建议您使用 查询对象模式 来解决此问题。基本上,您的服务可以有这样的签名:

IEnumerable<IData> GetData(IQuery<IData> query);

在 IQuery 接口内,您可以有一个方法,该方法将工作单元作为输入,例如事务上下文或 ISession 之类的东西(如果您使用的是诸如 NHibernate 之类的 ORM 并返回一个IData 对象列表。

public interface IQuery<T> 
{
 IEnumerable<T> DoQuery(IUnitOfWork unitOfWork);
}

这样,您可以创建符合您的要求的强类型查询对象,并为您的服务提供干净的接口。 Ayende 的这篇文章关于这个主题的好读物。

I would suggest you use the query object pattern to resolve this. Basically, your service could have a signature like:

IEnumerable<IData> GetData(IQuery<IData> query);

Inside the IQuery interface, you could have a method that takes a unit of work as input, for example a transaction context or something like ISession if you are using an ORM such as NHibernate and returns a list of IData objects.

public interface IQuery<T> 
{
 IEnumerable<T> DoQuery(IUnitOfWork unitOfWork);
}

This way, you can create strongly typed query objects that match your requirements, and have a clean interface for your services. This article from Ayende makes good reading about the subject.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文