开发者

Resolving a call-chain anti-pattern

开发者 https://www.devze.com 2023-02-18 05:47 出处:网络
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.

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开发者_C百科 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."


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.


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

public List<IData> GetData(IDataRequest request)


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.


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.


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


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.


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消