JSON 补丁和 "aggregate" DTO
JSON Patch and "aggregate" DTOs
一个有点做作但仍然很重要的例子。
假设以下情况 UserDetails
是一个聚合 DTO(不确定术语是否正确,请教我,但基本上是从不同 stores/services 收集的信息的模型),它被一个RESTful 网络服务。它不一定与它收集在一起的对象具有相同的 属性 名称。
public class UserDetails
{
public int UserId { get;set; }
public string GivenName { get; set; }
public string Surname { get; set; }
public int? UserGroupId { get;set; } // FK in a different database
}
让我们的店铺坚持以下模式:
public class User
{
public int Id { get; set; }
public string GivenName { get; set; }
public string Surname { get; set; }
}
public class UserGroup
{
public int UserId { get; set; }
public int GroupId { get; set; }
}
这样填充 UserDetails 对象:
User user = _userService.GetUser(userId) ?? throw new Exception();
UserGroup userGroup = _userGroupService.GetUserGroup(user.Id);
UserDetails userDetails = new UserDetails {
UserId = user.Id,
GivenName = user.GivenName,
Surname = user.Surname,
UserGroupId = userGroup?.GroupId
};
即设置FirstName
或Surname
应委托给UserService
,UserGroupId
应委托给GroupService
。
此 UserDetails
对象用于 GET 和 PUT,这里的逻辑非常简单,但是为 PATCH 请求发送了此对象的 JSON 补丁文档。这显然要复杂得多。
我们如何着手更改用户组?我想到的最好的('best' 被非常松散地使用)是这样的:
int userId;
JsonPatchDocument<UserDetails> patch;
// This likely works fine, because the properties in `UserDetails`
// are named the same as those in `User`
IEnumerable<string> userPaths = new List<string> {"/givenName", "/surname"};
if (patch.Operations.Any(x => userPaths.Contains(x.path))) {
User user = _userService.GetUserByUserId(userId);
patch.ApplyTo(user);
_userService.SetUser(userId, user);
}
// Do specialised stuff for UserGroup
// Can't do ApplyTo() because `UserDetails.UserGroupId` is not named the same as `UserGroup.GroupId`
IEnumerable<Operation<UserDetails>> groupOps = patch.Operations.Where(x => x.path == "/userGroupId");
foreach (Operation<UserDetails> op in groupOps)
{
switch (op.OperationType)
{
case OperationType.Add:
case OperationType.Replace:
_groupService.SetOrUpdateUserGroup(userId, (int?)(op.value));
break;
case OperationType.Remove:
_groupService.RemoveUserGroup(userId);
break;
}
}
这非常糟糕。它有很多样板,并且依赖于一个神奇的字符串。
不请求更改 Microsoft.AspNetCore.JsonPatch
API,类似
JsonPatchDocument<UserDetails> tmpPatch = new JsonPatchDocument<UserDetails>();
tmpPatch.Add(x => x.GivenName, String.Empty);
tmpPatch.Add(x => x.Surname, String.Empty);
IEnumerable<string> userPaths = tmpPatch.Operations.Select(x => x.path);
至少会摆脱魔术弦,但是,imo,这感觉不对!
JsonPatch 在这方面似乎非常有限,似乎更适合在 DAO(实体)和 DTO(模型)之间存在 1:1 映射的系统。
有人有什么好主意吗?不难打败我想出的肚!!
Json 合并补丁 - RFC7396 更适合这个。
一个有点做作但仍然很重要的例子。
假设以下情况 UserDetails
是一个聚合 DTO(不确定术语是否正确,请教我,但基本上是从不同 stores/services 收集的信息的模型),它被一个RESTful 网络服务。它不一定与它收集在一起的对象具有相同的 属性 名称。
public class UserDetails
{
public int UserId { get;set; }
public string GivenName { get; set; }
public string Surname { get; set; }
public int? UserGroupId { get;set; } // FK in a different database
}
让我们的店铺坚持以下模式:
public class User
{
public int Id { get; set; }
public string GivenName { get; set; }
public string Surname { get; set; }
}
public class UserGroup
{
public int UserId { get; set; }
public int GroupId { get; set; }
}
这样填充 UserDetails 对象:
User user = _userService.GetUser(userId) ?? throw new Exception();
UserGroup userGroup = _userGroupService.GetUserGroup(user.Id);
UserDetails userDetails = new UserDetails {
UserId = user.Id,
GivenName = user.GivenName,
Surname = user.Surname,
UserGroupId = userGroup?.GroupId
};
即设置FirstName
或Surname
应委托给UserService
,UserGroupId
应委托给GroupService
。
此 UserDetails
对象用于 GET 和 PUT,这里的逻辑非常简单,但是为 PATCH 请求发送了此对象的 JSON 补丁文档。这显然要复杂得多。
我们如何着手更改用户组?我想到的最好的('best' 被非常松散地使用)是这样的:
int userId;
JsonPatchDocument<UserDetails> patch;
// This likely works fine, because the properties in `UserDetails`
// are named the same as those in `User`
IEnumerable<string> userPaths = new List<string> {"/givenName", "/surname"};
if (patch.Operations.Any(x => userPaths.Contains(x.path))) {
User user = _userService.GetUserByUserId(userId);
patch.ApplyTo(user);
_userService.SetUser(userId, user);
}
// Do specialised stuff for UserGroup
// Can't do ApplyTo() because `UserDetails.UserGroupId` is not named the same as `UserGroup.GroupId`
IEnumerable<Operation<UserDetails>> groupOps = patch.Operations.Where(x => x.path == "/userGroupId");
foreach (Operation<UserDetails> op in groupOps)
{
switch (op.OperationType)
{
case OperationType.Add:
case OperationType.Replace:
_groupService.SetOrUpdateUserGroup(userId, (int?)(op.value));
break;
case OperationType.Remove:
_groupService.RemoveUserGroup(userId);
break;
}
}
这非常糟糕。它有很多样板,并且依赖于一个神奇的字符串。
不请求更改 Microsoft.AspNetCore.JsonPatch
API,类似
JsonPatchDocument<UserDetails> tmpPatch = new JsonPatchDocument<UserDetails>();
tmpPatch.Add(x => x.GivenName, String.Empty);
tmpPatch.Add(x => x.Surname, String.Empty);
IEnumerable<string> userPaths = tmpPatch.Operations.Select(x => x.path);
至少会摆脱魔术弦,但是,imo,这感觉不对!
JsonPatch 在这方面似乎非常有限,似乎更适合在 DAO(实体)和 DTO(模型)之间存在 1:1 映射的系统。
有人有什么好主意吗?不难打败我想出的肚!!
Json 合并补丁 - RFC7396 更适合这个。