我需要帮助设计API的JSON响应模型。
假设getUsers
调用返回以下响应模型:
{
"users":
[
{
"userId": "1",
"name": "Joe Soap",
"companyId": "3"
},
{
"userId": "2",
"name": "Bob Jones",
"companyId": "3"
},
{
"userId": "3",
"name": "Mary Jane",
"companyId": "4"
},
]
}
现在我们有另一个调用,比方说getUsersWithCompany
。这包括公司信息。我的问题是:
我应该将这个额外的company
数据包括在每个user
中,还是应该在响应模型中作为一个全新的companies
列表
解决方案1:用户与公司相结合
{
"usersWithCompany":
[
{
"userId": "1",
"name": "Joe Soap",
"company":
{
"companyId": "3",
"companyName": "Some Company Ltd",
"contactNumber": "123123123"
},
},
{
"userId": "2",
"name": "Mary Jane",
"company":
{
"companyId": "4",
"companyName": "Another Company Ltd",
"contactNumber": "463463383"
},
}
]
}
优点:在遍历用户列表时,当公司数据可用时,使用模型时,这可能会更容易。
解决方案2:用户和公司的单独列表
{
"users":
[
{
"userId": "1",
"name": "Joe Soap",
"companyId": "3"
},
{
"userId": "2",
"name": "Bob Jones",
"companyId": "3"
},
{
"userId": "3",
"name": "Mary Jane",
"companyId": "4"
},
],
"companies":
[
{
"companyId": "3",
"companyName": "Some Company Ltd",
"contactNumber": "123123123"
},
{
"companyId": "4",
"companyName": "Another Company Ltd",
"contactNumber": "463463383"
},
],
}
优点:这可确保用户和公司项目在呼叫中始终保持一致。消费者的种类较少。
我们正在使用。NET Web Api 2。
设计应该考虑实体是弱还是强。(弱表示如果删除主实体,它们就不存在)。所以对于用户和公司来说,我会有一个单独的RESTFULL服务,比如
/users
[
{
"userId": "1",
"name": "Joe Soap",
"companyId": "3"
},
{
"userId": "2",
"name": "Bob Jones",
"companyId": "3"
},
{
"userId": "3",
"name": "Mary Jane",
"companyId": "4"
},
]
/companies
[
{
"companyId": "3",
"companyName": "Some Company Ltd",
"contactNumber": "123123123"
},
{
"companyId": "4",
"companyName": "Another Company Ltd",
"contactNumber": "463463383"
},
]
您还需要考虑,除了用户之外,您是否还有其他对公司目录的参考。如果是这样的话,我甚至会对这种方法深信不疑。
我会从合并的公司和用户对象的单个列表开始(在双列表中承诺YAGNI"你不会需要它")。这是我见过的使用最广泛的解决方案,它允许您稍后轻松添加更多与用户相关的信息
稍后,如果负载大小成为一个问题(可能是因为一个响应中有太多用户),您可以添加一个新的方法/服务,返回结果的优化版本。但请记住,压缩输出可以免费获得长的wrt.payload大小!
但"优化版本"可能是不同的:你建议了一个解决方案(两个列表),但也许你后来发现,更好的优化是只返回某种"增量"或"增量"结果,而不是之前的响应。
这完全取决于你的用例——过早的优化会让你付出可能永远不需要的努力。
我建议将用户和公司视为不同的资源。您可以使用查询参数来指定是否在用户负载中包括公司信息。
GET /users
{
"users":
[
{
"userId": "1",
"name": "Joe Soap",
"company":
{
"self": "http:/my.server/companies/3"
}
},
...
]
}
GET /users?expand=company
{
"users":
[
{
"userId": "1",
"name": "Joe Soap",
"company":
{
"self": "http:/my.server/companies/3",
"companyId": "3",
"companyName": "Some Company Ltd",
"contactNumber": "123123123"
}
},
...
]
}
这允许您只在需要时获取用户信息,在需要时捆绑公司信息,并按照URI从特定用户到他们的公司,而无需在客户端上构建URI。通过将公司作为一个单独的资源,您也为自己提供了与公司合作的空间,而不需要用户作为参考点。
我看到公司如何连接用户,我写了一个,我选择了这种方法来解决这个问题
/用户返回用户列表/用户?connections=OUT返回所有连接都是用户模型的输出。
我认为你只能通过一条路线发送这些信息,例如
/用户返回用户列表
{
"users":
[
{
"userId": "1",
"name": "Joe Soap",
"companyId": "3"
},
{
"userId": "2",
"name": "Bob Jones",
"companyId": "3"
},
{
"userId": "3",
"name": "Mary Jane",
"companyId": "4"
},
]
}
/用户?company=真实回报
{
"users":
[
{
"userId": "1",
"name": "Joe Soap",
"company": {
"companyId": "3",
"companyName": "Some Company Ltd",
"contactNumber": "123123123"
},
},
{
"userId": "2",
"name": "Bob Jones",
"company": {
"companyId": "3",
"companyName": "Some Company Ltd",
"contactNumber": "123123123"
},
},
{
"userId": "3",
"name": "Mary Jane",
"company" : {
"companyId": "4",
"companyName": "Another Company Ltd",
"contactNumber": "463463383"
},
},
]
}
但如果我能在你的解决方案之间做出选择,我更喜欢第一个
我确实认为您需要包括这些用户所属的公司。但这里有几个关键点:
-
如果您返回完整的公司对象,使用您服务的人员将需要检查如果公司信息在每次通话中都发生了变化,那么会给用户增加更多的复杂性。我只返回公司id,然后使用相邻的服务来获取这些公司。
-
我强烈建议使用另一个字段来跟踪此用户信息何时更改,如时间戳或哈希。这将使您的服务更易于使用,因为人们不需要在数组上迭代来检查每个用户信息是否在每次调用中都发生了更改。这适用于您提供的每一项服务。
请记住,对于大量数据,返回一小部分数据会带来性能优势。
我建议使用第二种解决方案,因为
-
这样可以确保用户和公司项目始终一致跨电话(如您所述)。
-
无需为不同的API(如getUsers、getUsersWithCompany)维护单独的模型
-
信息不会有冗余。(这将节省网络带宽)
-
如果您将公司和用户绑定到UI模型,则很容易从基于Id的公司模型中获取详细信息。
-
如果公司详细信息有任何更改,则无需刷新/更新用户模型。
解决方案1是一致的:告诉您用户及其公司。
解决方案2不是。正在为您提供用户,顺便说一句,还有公司。同时做两件事。
您需要用户及其公司信息吗?然后向他们提供用户,以及每个用户的公司,这就是请求者想要的。
如果您将用户和公司分开,请求者将不得不将它们链接起来。这是不必要的,就好像请求者想要的公司会请求他们一样。请求者想要用户和他们的公司!
如果您正在为web设计接口,则应始终牢记响应时间。除了学术建筑问题,这将是你将面临的主要挑战之一。
问问自己:这个调用可以返回多少用户和公司实体的元组。请记住,随着时间的推移,结果会变得相当大。
你将努力保持这些界面的信息密度(=熵)非常高。这意味着,您试图消除多余的信息。
您的解决方案二实现了这些目标。它还有另一个好处:界面使用者更容易将响应分解为自己的响应。所有公司实体(希望)都是唯一的,与解决方案一中所需的检查相比,无需验证返回的公司实体的完整性和相似性。与其他类型的操作相比,对JSON进行编组和解编组,就像所有线性String操作一样,相当耗时。字符串越短,返回给使用者的结果就越快。
旁注:如果使用解决方案一并通过gzipped http连接发送此响应,与解决方案二相比,http通道上的加载时间不会慢很多。GZIP协议可以很好地消除您在公司实体中创建的这些字符串冗余。