您的位置:首页 > 移动开发 > Objective-C

ABP框架系列之十八:(Data-Transfer-Objects-数据转换对象)

2017-12-04 15:30 351 查看
Data Transfer Objects are used to transfer data between Application Layer and Presentation Layer.

数据传输对象用于在应用层和表示层之间传输数据。

Presentation Layer calls to an Application Service method with a Data Transfer Object (DTO), then application service uses domain objects to perform some specific business logic and returns a DTO back to the presentation layer. Thus, Presentation layer is completely isolated from Domain layer. In an ideally layered application, presentation layer does not directly work with domain objects (Repositories, Entities...).

表示层调用一个数据传输对象(DTO)应用服务的方法,然后应用服务使用域对象执行一些具体的业务逻辑,并返回一个DTO返回给表现层。因此,表示层与域层完全隔离。在理想的分层应用程序中,表示层不直接与域对象(存储库、实体……)一起工作。

The need for DTOs

To create a DTO for each Application Service method can be seen as a tedious and time-consuming work at first. But they can save your application if you correctly use it. Why?

创建一个DTO各应用服务的方法可以看出,首先作为一个繁琐和费时的工作。但如果你正确使用,他们可以保存你的应用程序。为什么?

Abstraction of domain layer(领域层抽象)

DTOs provide an efficient way of abstracting domain objects from presentation layer. Thus, your layers are correctly seperated. Even if you want to change presentation layer completely, you can continue with existing application and domain layers. As opposite, you can re-write your domain layer, completely change database schema, entities and O/RM framework without any change in presentation layer as long as contracts (method signatures and DTOs) of your application services remain unchanged.

DTO提供从表示层的抽象域对象的一种有效方法。因此,您的层是正确分离的。即使您想完全改变表示层,也可以继续使用现有的应用程序和域层。相反,你可以重写你的领域层,彻底改变数据库模式、实体和O / RM框架不在表示层的任何变化只要合同(方法签名和DTOs)你的应用程序的服务保持不变。

Data hiding(数据隐藏)

Think that you have a User entity with properties Id, Name, EmailAddress and Password fields. If GetAllUsers() method of UserAppService returns a List<User>, anyone can see passwords of all users, even you do not show it in the screen. It's not just about security, it's about data hiding. Application service should return to presentation layer what it needs. Not more, not less.

假设你有一个用户实体,包含属性ID,名字,地址和密码字段。如果userappservice getallusers()方法返回一个列表<用户名>,任何人都可以看到所有用户的密码,即使你不在屏幕上显示出来。它不仅仅是关于安全性的,它是关于数据隐藏的。应用程序服务应该返回到表示层它需要什么。不是更多,不是更少。

Serialization & lazy load problems(序列化和延迟加载问题)

When you return a data (an object) to presentation layer, it's probably serialized in somewhere. For example, in an MVC method that returns JSON, your object can be serialized to JSON and sent to the client. Returning an Entity to the presentation layer can be problematic in that case. How?

In a real application, your entities will have references to each other. User entity can have a reference to its Roles. So, if you want to serialize User, its Roles are also serialized. And even Role class may have a List<Permission> and Permission class can have a reference to a PermissionGroup class and so on... Can you think that all of these objects are serialized? You can easily serialize your whole database accidently. Also, if your objects has circcular references, it can not be serialized.

What's the solution? Marking properties as NonSerialized? No, you can not know when it should be serialized and when it shouldn't be. It may be needed in one application service method, may not be needed in other. So, returning a safely serializable, specially designed DTOs are a good choice in this situation.

Almost all O/RM frameworks support lazy-loading. It's a feature to load entities from database when it's needed. Say User class has a reference to Role class. When you get a User from database, Role property is not filled. When you first read the Role property, it's loaded from database. So, if you return such an Entity to presentation layer, it will cause to retrieve additonal entities from database. If a serialization tool reads the entity, it reads all properties recursively and again your complete database can be retrieved (if there are suitable relations between entities).

We can say some more problems about using Entities in presentation layer. It's best to do not reference the assembly containing domain (business) layer to the presentation layer at all.

当您将数据(对象)返回到表示层时,它可能在某处序列化。例如,在返回JSON的MVC方法中,您的对象可以序列化为JSON并发送给客户机。将实体返回到表示层在这种情况下可能是有问题的。怎么用?

在实际应用程序中,您的实体将相互引用。用户实体可以对其角色进行引用。所以,如果你想要连载的用户,其角色也连载。甚至角色类可能有一个列表<许可>和允许类可以有一个参考一permissiongroup类等…你认为所有这些对象都是序列化的吗?你可以很容易地将你的整个数据库的意外。另外,如果你的对象有自身的引用,它不能被序列化。

解决的办法是什么?标记性非序列化?不,你不知道什么时候应该序列化,什么时候不应该。它可能需要在一个应用程序服务方法,可能不需要在其他。所以,回到一个安全的可串行化的,在这种情况下,专门设计的点是一个很好的选择。

几乎所有的O/RM框架都支持延迟加载。在需要时从数据库加载实体是一个特性。表示用户类对角色类有引用。当从数据库中获取用户时,角色属性不被填充。当您第一次读取角色属性时,它是从数据库加载的。所以,如果你还这样一个实体的表示层,它会从数据库获取额外的实体。如果序列化工具读取该实体,则它将递归地读取所有属性,并且可以检索完整的数据库(如果实体之间有适当的关系)。

在表示层中使用实体可以说更多的问题。最好不要将包含域(业务)层的程序集引用到表示层。

DTO conventions & validation(约定和验证)

ASP.NET Boilerplate strongly supports DTOs. It provides some conventional classes & interfaces and advices some naming and usage conventions about DTOs. When you write your codes as described here, ASP.NET Boilerplate automates some tasks easily.

ASP.NET样板强烈支持DTOs。它提供了一些常规的类和接口,建议一些关于DTOs的命名和使用公约。当你写你的代码,这里所描述的,ASP.NET样板自动执行一些任务很容易。

Example

Let's see a complete example. Say that we want to develop an application service method that is used to search people with a name and returns list of people. In that case, we may have a Person entity as shown below:

public class Person : Entity
{
public virtual string Name { get; set; }
public virtual string EmailAddress { get; set; }
public virtual string Password { get; set; }
}

And we can define an interface for our application service:

public interface IPersonAppService : IApplicationService
{
SearchPeopleOutput SearchPeople(SearchPeopleInput input);
}

ASP.NET Boilerplate suggest naming input/output parameters as MethodNameInput and MethodNameOutput and defining a seperated input and output DTO for every application service method. Even if your method only take/return one parameter, it's better to create a DTO class. Thus, your code will be more extensible. You can add more properties later without changing signature of your method and without breaking existing client applications.

Surely, your method can return void if there is no return value. It will not break existing applications if you add a return value later. If your method does not get any argument, you do not have to define an input DTO. But it maybe better to write an input DTO class if it's probable to add parameter in the future. This is up to you.

ASP.NET样板建议命名输入/输出methodnameinput和methodnameoutput定义分离的输入和输出的DTO为每个应用服务方法参数。即使你的方法只需要/返回一个参数,它是更好地创建一个DTO类。因此,您的代码将具有更大的可扩展性。您可以在不改变方法签名的情况下添加更多的属性,而不会破坏现有的客户端应用程序。

当然,如果没有返回值,则您的方法可以返回空值。如果稍后添加返回值,它不会破坏现有的应用程序。如果你的方法没有得到任何的说法,你不需要定义一个输入DTO。但也许最好写输入DTO类如果在将来添加参数是可能的。这取决于你。

Let's see input and output DTO classes defined for this example:

public class SearchPeopleInput
{
[StringLength(40, MinimumLength = 1)]
public string SearchedName { get; set; }
}

public class SearchPeopleOutput
{
public List<PersonDto> People { get; set; }
}

public class PersonDto : EntityDto
{
public string Name { get; set; }
public string EmailAddress { get; set; }
}

ASP.NET Boilerplate automatically validates input before execution of the method. It's similar to ASP.NET MVC's validation, but notice that application service is not a Controller, it's a plain C# class. ASP.NET Boilerplate makes interception and check input automatically. There are much more about validation. See DTO validation document.

EntityDto is a simple class that declares Id property since they are common for entities. Has a generic version if your entity's primary key is not int. You don't have to use it, but can be better to define an Id property.

PersonDto does not include Password property as you see, since it's not needed for presentation layer. Even it can be dangerous to send all people's password to presentation layer. Think that a Javascript client requested it, anyone can easily grab all passwords.

ASP.NET样板自动验证输入之前执行的方法。这是类似于ASP.NET的MVC的验证,但注意到应用服务不是一个控制器,它是一个普通的C #类。ASP.NET样板进行拦截和检查自动输入。还有更多关于验证的内容。See DTO validation document.

entitydto是一个简单的类声明ID属性因为他们是常见的实体。如果你的实体的主键不是int型的,那么它就有一个通用的版本,你不必使用它,但是可以更好地定义id属性。

persondto不包括密码特性如你所看到的,因为它不是为表示层需要。甚至把所有人的密码发送到表示层也是很危险的。认为一个JavaScript客户端请求它,任何人都可以轻松地获取所有密码。

Let's implement IPersonAppService before go further.

public class PersonAppService : IPersonAppService
{
private readonly IPersonRepository _personRepository;

public PersonAppService(IPersonRepository personRepository)
{
_personRepository = personRepository;
}

public SearchPeopleOutput SearchPeople(SearchPeopleInput input)
{
//Get entities
var peopleEntityList = _personRepository.GetAllList(person => person.Name.Contains(input.SearchedName));

//Convert to DTOs
var peopleDtoList = peopleEntityList
.Select(person => new PersonDto
{
Id = person.Id,
Name = person.Name,
EmailAddress = person.EmailAddress
}).ToList();

return new SearchPeopleOutput { People = peopleDtoList };
}
}

We get entities from database, convert them to DTOs and return output. Notice that we did not validated input. ASP.NET Boilerplate validates it. It even checks if input parameter is null and throws exception if so. This saves us to write guard clauses in every method.

But, probably you did not like converting code from a Person entity to a PersonDto object. It's really a tedious work. Person entity could have much more properties.

我们得到的实体数据库,将其转换为DTOs和返回输出。注意,我们没有验证输入。ASP.NET样板验证它。它甚至检查输入参数是否为null,并抛出异常。这就节省了我们在每个方法中写保护子句的能力。

但是,你可能不喜欢将代码从一person到一个persondto实体对象。这真是一件单调乏味的工作。实体可以拥有更多的属性。

Auto mapping between DTOs and entities

Fortunately there are tools that makes this very easy. AutoMapper is one of them. See AutoMapper Integration document to know how to use it.

Helper interfaces and classes

ASP.NET provides some helper interfaces that can be implemented to standardize common DTO property names.

ILimitedResultRequest defines MaxResultCount property. So, you can implement it in your input DTOs to standardize limiting result set.

IPagedResultRequest extends ILimitedResultRequest by adding SkipCount. So, we can implement this interface in SearchPeopleInput for paging:

ASP.NET提供了一些辅助接口,可以实现规范普通DTO属性名称。

ilimitedresultrequest maxresultcount属性定义。所以,你可以实现它在你输入DTOs规范限制结果集。

ipagedresultrequest延伸ilimitedresultrequest加入skipcount。所以,我们可以实现这个接口在SearchPeopleInput分页:

public class SearchPeopleInput : IPagedResultRequest
{
[StringLength(40, MinimumLength = 1)]
public string SearchedName { get; set; }

public int MaxResultCount { get; set; }
public int SkipCount { get; set; }
}

As a result to a paged request, you can return an output DTO that implements IHasTotalCount. Naming standardization helps us to create re-usable codes and conventions. See other interfaces and classes underAbp.Application.Services.Dto namespace.

作为一个结果,一个页面的请求,你可以返回一个输出,实现了ihastotalcount DTO。命名标准化有助于我们创建可重用的代码和约定。看到其他的接口和类underabp.application.services.dto命名空间。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: