Soliciting feedback/options/comments regarding a "best" pattern to use for reference data in my services.
征求关于“最佳”模式的反馈/选项/评论,以用于我的服务中的参考数据。
What do I mean by reference data?
参考数据是什么意思?
Let's use Northwind as an example. An Order is related to a Customer in the database. When I implement my Orders Service, in some cases I'll want the reference a "full" Customer from an Order and other cases when I just want a reference to the Customer (for example a Key/Value pair).
我们以Northwind为例。订单与数据库中的客户相关。当我实现我的订单服务时,在某些情况下,当我只想要一个对客户的引用(例如一个键/值对)时,我会希望引用来自订单的“完整”客户和其他情况。
For example, if I were doing a GetAllOrders(), I wouldn't want to return a fully filled out Order, I'd want to return a lightweight version of an Order with only reference data for each order's Customer. If I did a GetOrder() method, though, I'd probably want to fill in the Customer details because chances are a consumer of this method might need it. There might be other situations where I might want to ask that the Customer details be filled in during certain method calls, but left out for others.
例如,如果我正在使用GetAllOrders(),我不想返回完全填写的订单,我想要返回订单的轻量版本,其中只包含每个订单的客户的参考数据。但是,如果我使用GetOrder()方法,我可能想填写客户详细信息,因为很可能需要此方法的消费者。可能还有其他情况我可能要求在某些方法调用期间填写客户详细信息,但留给其他人使用。
Here is what I've come up with:
这是我提出的:
[DataContract]
public OrderDTO
{
[DataMember(Required)]
public CustomerDTO;
//etc..
}
[DataContract]
public CustomerDTO
{
[DataMember(Required)]
public ReferenceInfo ReferenceInfo;
[DataMember(Optional)]
public CustomerInfo CustomerInfo;
}
[DataContract]
public ReferenceInfo
{
[DataMember(Required)]
public string Key;
[DataMember(Required)]
public string Value;
}
[DataContract]
public CustomerInfo
{
[DataMember(Required)]
public string CustomerID;
[DataMember(Required)]
public string Name;
//etc....
}
The thinking here is that since ReferenceInfo (which is a generic Key/Value pair) is always required in CustomerDTO, I'll always have ReferenceInfo. It gives me enough information to obtain the Customer details later if needed. The downside to having CustomerDTO require ReferenceInfo is that it might be overkill when I am getting the full CustomerDTO (i.e. with CustomerInfo filled in), but at least I am guaranteed the reference info.
这里的想法是,因为在CustomerDTO中总是需要ReferenceInfo(它是一个通用的Key / Value对),所以我总是有ReferenceInfo。如果需要,它可以为我提供足够的信息以获取客户详细信息。让CustomerDTO需要ReferenceInfo的缺点是,当我获得完整的CustomerDTO(即填写了CustomerInfo)时,它可能有点过分,但至少我保证参考信息。
Is there some other pattern or framework piece I can use to make this scenario/implementation "cleaner"?
我可以使用其他一些模式或框架来使这个场景/实现“更清洁”吗?
The reason I ask is that although we could simply say in Northwind to ALWAYS return a full CustomerDTO, that might work fine in the simplistic Northwind situation. In my case, I have an object that has 25-50 fields that are reference/lookup type data. Some are more important to load than others in different situations, but i'd like to have as few definitions of these reference types as possible (so that I don't get into "DTO maintenance hell").
我问的原因是,尽管我们可以简单地说在Northwind中总是返回一个完整的CustomerDTO,但这在单纯的Northwind情况下可能会正常工作。在我的例子中,我有一个对象,有25-50个字段是引用/查找类型数据。有些在不同的情况下加载比其他更重要,但我希望尽可能少地定义这些引用类型(这样我就不会进入“DTO维护地狱”)。
Opinions? Feedback? Comments?
意见?反馈?评论?
Thanks!
7 个解决方案
#1
We're at the same decision point on our project. As of right now, we've decided to create three levels of DTOs to handle a Thing: SimpleThing, ComplexThing, and FullThing. We don't know how it'll work out for us, though, so this is not yet an answer grounded in reality.
我们的项目处于同一决策点。截至目前,我们已决定创建三个级别的DTO来处理Thing:SimpleThing,ComplexThing和FullThing。但是,我们不知道它对我们有什么影响,所以这还不是基于现实的答案。
One thing I'm wondering is if we might learn that our services are designed at the "wrong" level. For example, is there ever an instance where we should bust a FullThing apart and only pass a SimpleThing? If we do, does that imply we've inappropriately put some business logic at too high of a level?
我想知道的一件事是,我们是否可以了解到我们的服务是在“错误”的水平设计的。例如,是否有一个实例我们应该将FullThing分开并仅通过SimpleThing?如果我们这样做,这是否意味着我们不恰当地将某些业务逻辑置于过高的水平?
#2
Amazon Product Advertising API Web service is a good example of the same problem that you are experiencing.
亚马逊产品广告API Web服务是您遇到的同一问题的一个很好的例子。
They use different DTOs to provide callers with more or less detail depending on their circumstances. For example there is the small response group, the large response group and in the middle medium response group.
他们根据具体情况使用不同的DTO为呼叫者提供或多或少的细节。例如,存在小响应组,大响应组和中间响应组。
Having different DTOs is a good technique if as you say you don't want a chatty interface.
如果您不想要一个讨人喜欢的界面,拥有不同的DTO是一种很好的技术。
#3
It seems like a complicated solution to me. Why not just have a customer id field in the OrderDTO class and then let the application decide at runtime whether it needs the customer data. Since it has the customer id it can pull the data down when it so decides.
对我来说这似乎是一个复杂的解决方案。为什么不在OrderDTO类中只有一个customer id字段,然后让应用程序在运行时决定它是否需要客户数据。由于它具有客户ID,因此可以在决定时将数据拉下来。
#4
I've decided against the approach I was going to take. I think much of my initial concerns were a result of a lack of requirements. I sort of expected this to be the case, but was curious to see how others might have tackled this issue of determining when to load up certain data and when not to.
我决定反对我要采取的方法。我认为我最初的担忧很大程度上是由于缺乏要求。我有点期待这种情况,但很想知道其他人如何处理这个确定何时加载某些数据以及何时不加载的问题。
I am flattening my Data Contract to contain the most used fields of reference data elements. This should work for a majority of consumers. If the supplied data is not enough for a given consumer, they'll have the option to query a separate service to pull back the full details for a particular reference entity (for example a Currency, State, etc). For simple lookups that really are basically Key/Value pairs, we'll be handling them with a generic Key/Value pair Data Contract. I might even use the KnownType attribute for my more specialized Key/Value pairs.
我正在展平我的数据合同以包含最常用的参考数据元素字段。这应该适用于大多数消费者。如果提供的数据对于给定的消费者来说不够,他们可以选择查询单独的服务以撤回特定参考实体的完整详细信息(例如货币,州等)。对于实际上基本上是键/值对的简单查找,我们将使用通用键/值对数据契约来处理它们。我甚至可以将KnownType属性用于更专业的键/值对。
[DataContract]
public OrderDTO
{
[DataMember(Required)]
public CustomerDTO Customer;
//in this case, I think consumers will need currency data,
//so I pass back a full currency item
[DataMember(Required)]
public Currency Currency;
//in this case, I think consumers are not likely to need full StateRegion data,
//so I pass back a "reference" to it
//User's can call a separate service method to get full details if needed, or
[DataMember(Required)]
public KeyValuePair ShipToStateRegion;
//etc..
}
[DataContract]
[KnownType(Currency)]
public KeyValuePair
{
[DataMember(Required)]
public string Key;
[DataMember(Required)]
public string Value;
//enum consisting of all possible reference types,
//such as "Currency", "StateRegion", "Country", etc.
[DataMember(Required)]
public ReferenceType ReferenceType;
}
[DataContract]
public Currency : KeyValuePair
{
[DataMember(Required)]
public decimal ExchangeRate;
[DataMember(Required)]
public DateTime ExchangeRateAsOfDate;
}
[DataContract]
public CustomerDTO
{
[DataMember(Required)]
public string CustomerID;
[DataMember(Required)]
public string Name;
//etc....
}
Thoughts? Opinions? Comments?
思考?意见?评论?
#5
We've faced this problem in object-relational mapping as well. There are situations where we want the full object and others where we want a reference to it.
我们也在对象关系映射中遇到了这个问题。在某些情况下,我们需要完整的对象以及我们想要引用它的其他对象。
The difficulty is that by baking the serialization into the classes themselves, the datacontract pattern enforces the idea that there's only one right way to serialize an object. But there are lots of scenarios where you might want to partially serialize a class and/or its child objects.
难点在于,通过将序列化本身烘焙到类本身中,datacontract模式强制认为只有一种正确的方法来序列化对象。但是在很多场景中,您可能希望部分地序列化类和/或其子对象。
This usually means that you have to have multiple DTOs for each class. For example, a FullCustomerDTO and a CustomerReferenceDTO. Then you have to create ways to map the different DTOs back to the Customer domain object.
这通常意味着每个班级必须有多个DTO。例如,FullCustomerDTO和CustomerReferenceDTO。然后,您必须创建将不同DTO映射回Customer域对象的方法。
As you can imagine, it's a ton of work, most of it very tedious.
你可以想象,这是一项大量的工作,大部分工作都非常繁琐。
#6
One other possibility is to treat the objects as property bags. Specify the properties you want when querying, and get back exactly the properties you need.
另一种可能性是将物体视为物品袋。在查询时指定所需的属性,并准确返回所需的属性。
Changing the properties to show in the "short" version then won't require multiple round trips, you can get all of the properties for a set at one time (avoiding chatty interfaces), and you don't have to modify your data or operation contracts if you decide you need different properties for the "short" version.
更改要在“短”版本中显示的属性则不需要多次往返,您可以一次获取一组的所有属性(避免繁琐的接口),并且您不必修改数据或如果您决定需要“短”版本的不同属性,则运营合同。
#7
I typically build in lazy loading to my complex web services (ie web services that send/receive entities). If a Person has a Father property (also a Person), I send just an identifier for the Father instead of the nested object, then I just make sure my web service has an operation that can accept an identifier and respond with the corresponding Person entity. The client can then call the web service back if it wants to use the Father property.
我通常构建延迟加载到我的复杂Web服务(即发送/接收实体的Web服务)。如果一个Person有一个父属性(也是一个Person),我只发送一个父的标识符而不是嵌套对象,然后我只是确保我的web服务有一个可以接受一个标识符的操作并用相应的Person实体作出响应。然后,如果客户端想要使用父属性,则可以回调用该Web服务。
I've also expanded on this so that batching can occur. If an operation sends back 5 Persons, then if the Father property is accessed on any one of those Persons, then a request is made for all 5 Fathers with their identifiers. This helps reduce the chattiness of the web service.
我也对此进行了扩展,以便可以进行批处理。如果一个操作发回5个人,那么如果在其中任何一个人*问了父属性,则会向所有5个父亲发出一个请求,并提供他们的标识符。这有助于减少Web服务的干扰。
#1
We're at the same decision point on our project. As of right now, we've decided to create three levels of DTOs to handle a Thing: SimpleThing, ComplexThing, and FullThing. We don't know how it'll work out for us, though, so this is not yet an answer grounded in reality.
我们的项目处于同一决策点。截至目前,我们已决定创建三个级别的DTO来处理Thing:SimpleThing,ComplexThing和FullThing。但是,我们不知道它对我们有什么影响,所以这还不是基于现实的答案。
One thing I'm wondering is if we might learn that our services are designed at the "wrong" level. For example, is there ever an instance where we should bust a FullThing apart and only pass a SimpleThing? If we do, does that imply we've inappropriately put some business logic at too high of a level?
我想知道的一件事是,我们是否可以了解到我们的服务是在“错误”的水平设计的。例如,是否有一个实例我们应该将FullThing分开并仅通过SimpleThing?如果我们这样做,这是否意味着我们不恰当地将某些业务逻辑置于过高的水平?
#2
Amazon Product Advertising API Web service is a good example of the same problem that you are experiencing.
亚马逊产品广告API Web服务是您遇到的同一问题的一个很好的例子。
They use different DTOs to provide callers with more or less detail depending on their circumstances. For example there is the small response group, the large response group and in the middle medium response group.
他们根据具体情况使用不同的DTO为呼叫者提供或多或少的细节。例如,存在小响应组,大响应组和中间响应组。
Having different DTOs is a good technique if as you say you don't want a chatty interface.
如果您不想要一个讨人喜欢的界面,拥有不同的DTO是一种很好的技术。
#3
It seems like a complicated solution to me. Why not just have a customer id field in the OrderDTO class and then let the application decide at runtime whether it needs the customer data. Since it has the customer id it can pull the data down when it so decides.
对我来说这似乎是一个复杂的解决方案。为什么不在OrderDTO类中只有一个customer id字段,然后让应用程序在运行时决定它是否需要客户数据。由于它具有客户ID,因此可以在决定时将数据拉下来。
#4
I've decided against the approach I was going to take. I think much of my initial concerns were a result of a lack of requirements. I sort of expected this to be the case, but was curious to see how others might have tackled this issue of determining when to load up certain data and when not to.
我决定反对我要采取的方法。我认为我最初的担忧很大程度上是由于缺乏要求。我有点期待这种情况,但很想知道其他人如何处理这个确定何时加载某些数据以及何时不加载的问题。
I am flattening my Data Contract to contain the most used fields of reference data elements. This should work for a majority of consumers. If the supplied data is not enough for a given consumer, they'll have the option to query a separate service to pull back the full details for a particular reference entity (for example a Currency, State, etc). For simple lookups that really are basically Key/Value pairs, we'll be handling them with a generic Key/Value pair Data Contract. I might even use the KnownType attribute for my more specialized Key/Value pairs.
我正在展平我的数据合同以包含最常用的参考数据元素字段。这应该适用于大多数消费者。如果提供的数据对于给定的消费者来说不够,他们可以选择查询单独的服务以撤回特定参考实体的完整详细信息(例如货币,州等)。对于实际上基本上是键/值对的简单查找,我们将使用通用键/值对数据契约来处理它们。我甚至可以将KnownType属性用于更专业的键/值对。
[DataContract]
public OrderDTO
{
[DataMember(Required)]
public CustomerDTO Customer;
//in this case, I think consumers will need currency data,
//so I pass back a full currency item
[DataMember(Required)]
public Currency Currency;
//in this case, I think consumers are not likely to need full StateRegion data,
//so I pass back a "reference" to it
//User's can call a separate service method to get full details if needed, or
[DataMember(Required)]
public KeyValuePair ShipToStateRegion;
//etc..
}
[DataContract]
[KnownType(Currency)]
public KeyValuePair
{
[DataMember(Required)]
public string Key;
[DataMember(Required)]
public string Value;
//enum consisting of all possible reference types,
//such as "Currency", "StateRegion", "Country", etc.
[DataMember(Required)]
public ReferenceType ReferenceType;
}
[DataContract]
public Currency : KeyValuePair
{
[DataMember(Required)]
public decimal ExchangeRate;
[DataMember(Required)]
public DateTime ExchangeRateAsOfDate;
}
[DataContract]
public CustomerDTO
{
[DataMember(Required)]
public string CustomerID;
[DataMember(Required)]
public string Name;
//etc....
}
Thoughts? Opinions? Comments?
思考?意见?评论?
#5
We've faced this problem in object-relational mapping as well. There are situations where we want the full object and others where we want a reference to it.
我们也在对象关系映射中遇到了这个问题。在某些情况下,我们需要完整的对象以及我们想要引用它的其他对象。
The difficulty is that by baking the serialization into the classes themselves, the datacontract pattern enforces the idea that there's only one right way to serialize an object. But there are lots of scenarios where you might want to partially serialize a class and/or its child objects.
难点在于,通过将序列化本身烘焙到类本身中,datacontract模式强制认为只有一种正确的方法来序列化对象。但是在很多场景中,您可能希望部分地序列化类和/或其子对象。
This usually means that you have to have multiple DTOs for each class. For example, a FullCustomerDTO and a CustomerReferenceDTO. Then you have to create ways to map the different DTOs back to the Customer domain object.
这通常意味着每个班级必须有多个DTO。例如,FullCustomerDTO和CustomerReferenceDTO。然后,您必须创建将不同DTO映射回Customer域对象的方法。
As you can imagine, it's a ton of work, most of it very tedious.
你可以想象,这是一项大量的工作,大部分工作都非常繁琐。
#6
One other possibility is to treat the objects as property bags. Specify the properties you want when querying, and get back exactly the properties you need.
另一种可能性是将物体视为物品袋。在查询时指定所需的属性,并准确返回所需的属性。
Changing the properties to show in the "short" version then won't require multiple round trips, you can get all of the properties for a set at one time (avoiding chatty interfaces), and you don't have to modify your data or operation contracts if you decide you need different properties for the "short" version.
更改要在“短”版本中显示的属性则不需要多次往返,您可以一次获取一组的所有属性(避免繁琐的接口),并且您不必修改数据或如果您决定需要“短”版本的不同属性,则运营合同。
#7
I typically build in lazy loading to my complex web services (ie web services that send/receive entities). If a Person has a Father property (also a Person), I send just an identifier for the Father instead of the nested object, then I just make sure my web service has an operation that can accept an identifier and respond with the corresponding Person entity. The client can then call the web service back if it wants to use the Father property.
我通常构建延迟加载到我的复杂Web服务(即发送/接收实体的Web服务)。如果一个Person有一个父属性(也是一个Person),我只发送一个父的标识符而不是嵌套对象,然后我只是确保我的web服务有一个可以接受一个标识符的操作并用相应的Person实体作出响应。然后,如果客户端想要使用父属性,则可以回调用该Web服务。
I've also expanded on this so that batching can occur. If an operation sends back 5 Persons, then if the Father property is accessed on any one of those Persons, then a request is made for all 5 Fathers with their identifiers. This helps reduce the chattiness of the web service.
我也对此进行了扩展,以便可以进行批处理。如果一个操作发回5个人,那么如果在其中任何一个人*问了父属性,则会向所有5个父亲发出一个请求,并提供他们的标识符。这有助于减少Web服务的干扰。