Is Entities on Client side anti-pattern?

2019-08-12 09:00发布

问题:

I have used RIA service before, now testing Breeze Sharp.

RIA as well as Breeze give an impression that what you see on the server/middle tier is what you see on the client. To support that, the term Entity is being used on both the client and the server. Is it really an Entity, or it really a Presentation Model or Model on the client?

For smaller systems having one or two level entity graphs, there may not be wrong in thinking both the client and the server is the same. For larger systems with graphs going deep into five or six levels, the entities need to be converted to a DTO to make it simple. Unless the UI has some CRUD screens for entities, large applications end up with more DTOs and less entities. Most of the time, these DTOs will represent what UI wants and is equivalent to a presentation model.

Why can't we consider what we deal with at the client as presentation models rather than entities?

回答1:

You are free to call the client-side entity class whatever you like :-)

More seriously, let's get at the typical reasoning behind the claim that this is an anti-pattern.

The client is not the presentation layer

I want to be super clear about this. Breeze is designed for rich web client applications. A Breeze client is not a presentation layer; it has a presentation layer. It also has its own business model and data access layers.

The terms "entity" and "DTO" mean different things to different people. I like Evan's DDD definition for "entity" and Fowler's definition for "DTO" in PoEAA.

Breeze client entities qualify as Evans entities: "Objects that have a distinct identity that runs through time and different representations. You also hear these called 'reference objects'" [Fowler]. Breeze entities aren't just property bags; they have business logic as well and you can extend them with more of your own.

Breeze entities are not "presentation models". They are independent of any particular UI representation and typically do not implement presentation concerns.

They are designed such that they can be bound directly to visual controls. That's a Breeze productivity design decision ... a decision about how we implement entities. Some people - the people who think entity properties are an anti-pattern - will hate that. Evans is silent on that question. Fowler poo-poos it. If it offends you, you may not like Breeze. Move along.

Send Entities or DTOs?

I'm about to argue that this is a false dichotomy.

People often say "it's an anti-pattern to send entities over the wire. Always send DTOs". There is sound reasoning behind this poorly worded edict. When a client and server entity class are identical, you've coupled the server's implementation to the client's implementation. If the model changes on the server, it must change on the client and vice-versa even if the change is only relevant on one of the tiers. That can interfere with your ability to evolve the server and client code independently. We may accept that coupling as a matter of expedience (and expedience matters!), but no one wants it.

A Breeze client entity class does not have to be the same, neither in shape nor in business logic, as the server entity class. When you query in Breeze, you put entity data on the wire and transform it into client entities; when you save, you put client entity data on the wire and transform it on the server into server entities. DTOs may be involved in either direction. The important fact is that the classes can be different.

They are conceptually related of course. You'll have a devil of a time transforming the data between the two representations if the meaning of the Customer entity diverges widely on the two sides. That's true with or without explicit DTOs.

Let's acknowledge as well that it is easier to transform the data in both directions when the classes are actually the same. You pay a mapping tax when they are different and you may lose the ability to compose Breeze LINQ queries on the client. You can pay the tax if you wish. Breeze doesn't care.

My inclination is to start with the same classes on both sides and change them when and as necessary. That has worked well for a high percentage of classes in RIA Services and DevForce. Most importantly, it has never been difficult for me to re-factor to separate classes when the need arose.

<rant> The worrywarts exaggerate the risks of sharing class definitions and understate the cost of mapping layers whose benefits are rarely realized in practice during the lifetime of the application.</rant>

When to use presentation models

You wrote:

For larger systems with graphs going deep into five or six levels, the entities need to be converted to a DTO to make it simple. ... Most of the time, these DTOs will represent what UI wants and is equivalent to a presentation model

In my experience that is only true if you assume that your client simply pastes entities to the screen. But I have already stipulated that the client is an application, not the presentation layer.

I further argue that you need a domain model on the client for the same reason that you need one on the server: to reason about the domain. You do this independently of the presentation. I assume that your entities will appear in some fashion on multiple screens subject to differing presentation rules. It's the same model, presented many ways. We call this "pivoting around the data".

No matter how many faces you put on the model, the underlying model data and the business rules that govern them should stay the same. That's what makes it a "Domain Model" and not a "Presentation Model."

FWIW, I always have a "Presentation Model" (AKA "ViewModel") in my apps to orchestrate the activities of a View. So I don't ask myself "PM or Model?". Rather I choose either to data bind visual controls directly to model entities that I expose through the VM's api or I bind them instead to an intermediate "Item Presentation Model" (AKA "Item ViewModel") that wraps some entities. Which way I go is an application decision. In practice, I start by binding directly to the entities and refactor to an "Item ViewModel" when and as needed.

In either case, I will construct the PMs (VMs) that I need on the client. If I need an "Item ViewModel", I create that on the client too. I do not ask my server to prepare DTOs for my client to display. To me that is an anti-pattern because it couples the server to the client.

How? If the developer needs to change a screen on the client, she may have to wait for someone to provide the supporting server endpoint and DTO. Now we have to coordinate the release schedules of the server and client even though the impetus for the change was a client requirement, not a server requirement.

Service pollution

It's actually worse than that. Some server-side developer has to stop what she's doing and add a new service method to satisfy a client requirement. That wasn't one of her requirements ... but it is now. Over time the service API expands enormously and soon it is full of look-a-like members that do apparently the same job in slightly different ways.

Eventually we forget who is using which method and what for. No one dares change an existing method for fear of breaking an unknown client. So the dev copies something that kind of looks right, make it a little different, and calls it something else. This pattern of service API pollution should sound familiar to anyone who has worked with enterprise applications.

Making exceptions

Every seeming "rule" is meant to be broken. Of course there are occasions when it is both expedient and efficient to let the server prepare data for display. This happens most frequently with high volume, read-only data that summarize an even larger volume of complex data on the Data Tier. When I go this route, I'm typically motivated by performance considerations. Otherwise, I stay true to the entity-oriented architecture.

When it looks like everything in my app conforms to the exception, I conclude that I've got the wrong architecture for this particular application ... and this shouldn't be a Breeze app. I don't know if this is your case or not.

Hope this helps.



回答2:

I don't know whether I like the idea of having another domain model on the client. This will lead to the legendary problem we all have struggled for years, that is distributing the business logic to the client. I mean the frontend like Windows Forms or XAML application or HTML5 provided that the app is connected to a server. Isn't it maintainable if the entities exist only in the domain layer in the server, and is shielded by an application service?

As I understand from DDD textbooks, when the model is an entity, it has behavior and data. A presentation model concerns about only the presentation of the domain entities that live on the server. There is validation for these models. This can be simple as an email is required to a business rule validations. When the client deals with complex business rule validations, it may be using the same rules that have been implemented in the domain layer. The advantage with frameworks like RIA is to share these business rules to the client, so duplication can be avoided. This will also force the same entities to be pushed to the client.

I favor the idea of the client and the server models to grow separately - model on the client and domain entities on the server. The cost involved is the translation of models, blotting app service, roundtrips to validate if the business rules are complex. You mentioned about some of these in your answer.