我正在评估Spring Data REST作为基于AngularJS的应用程序的后端。我很快将我们的域建模为一组聚合根,并遇到以下设计障碍:
我希望HAL _links 将属性放在每个任务JSON对象中,但遗憾的是,这些属性只能作为JSON构造根目录的链接显示。
E.g。我明白了:
{
"version": 0,
"name": "myModel",
"tasks": [
{
"name": "task1"
},
{
"name": "task2"
}
],
"_links": {
"self": {
"href": "http://localhost:8080/models/1"
},
"attributes": {
"href": "http://localhost:8080/models/1/attributes"
}
}
}
而不是我想成像的东西:
{
"version": 0,
"name": "myModel",
"tasks": [
{
"name": "task1",
"_links": {
"attributes": {
"href": "http://localhost:8080/models/1/tasks/1/attributes"
}
}
},
{
"name": "task2",
"_links": {
"attributes": {
"href": "http://localhost:8080/models/1/tasks/2/attributes"
}
}
],
"_links": {
"self": {
"href": "http://localhost:8080/models/1"
},
"attributes": {
"href": "http://localhost:8080/models/1/attributes"
}
}
}
顺便提一下,在第一个示例中,属性链接以404结尾。
我还没有看到HAL规范中的任何内容来处理这种情况,也没有在Spring Data REST文档中看到过。显然,我可以将任务定义为解决问题的资源,但是我的模型不需要这个。我觉得这是一个合法的用例。
我创建了一个简单的Spring Boot应用程序来重现这个问题。模特:
@Entity
public class Model {
@Id @GeneratedValue public Long id;
@Version public Long version;
public String name;
@OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
public List<Task> tasks;
}
@Entity
public class Task {
@Id @GeneratedValue public Long id;
public String name;
@ManyToMany
public Set<Attribute> attributes;
}
@Entity
public class Attribute {
@Id @GeneratedValue public Long id;
@Version public Long version;
public String name;
}
和存储库:
@RepositoryRestResource
public interface ModelRepository extends PagingAndSortingRepository<Model, Long> {
}
@RepositoryRestResource
public interface AttributeRepository extends PagingAndSortingRepository<Attribute,Long> {
}
在那里,我可能错过了一些东西,因为这似乎是一个非常简单的用例,但在SO上找不到任何有类似问题的人。也许,这可能是我模型中的一个根本缺陷,如果是这样,我准备好听你的论点: - )
答案 0 :(得分:1)
由于Spring Data REST本身不处理问题中描述的用例,因此第一步是停用Task任务属性的管理,并确保默认情况下不对它们进行序列化。这里@RestResource(exported=false)
确保不会为&#34;属性&#34;自动生成(非工作)链接。 rel,@JsonIgnore
确保默认情况下不会呈现属性。
@Entity
public class Task {
@Id
@GeneratedValue
public Long id;
public String name;
@ManyToMany
@RestResource(exported = false)
@JsonIgnore
public List<Attribute> attributes;
}
接下来,_links
属性仅在我们资源的根目录中可用,因此我选择实现名为&#34; taskAttributes&#34;的新rel,它将具有多个值,每个值一个任务。要将这些链接添加到资源,我构建了自定义ResourceProcessor
,并实现了实际的端点,自定义ModelController
:
@Component
public class ModelResourceProcessor implements ResourceProcessor<Resource<Model>> {
@Override
public Resource<Model> process(Resource<Model> modelResource) {
Model model = modelResource.getContent();
for (int i = 0; i < model.tasks.size(); i++) {
modelResource.add(linkTo(ModelController.class, model.id)
.slash("task")
.slash(i)
.slash("attributes")
.withRel("taskAttributes"));
}
return modelResource;
}
}
@RepositoryRestController
@RequestMapping("/models/{id}")
public class ModelController {
@RequestMapping(value = "/task/{index}/attributes", method = RequestMethod.GET)
public ResponseEntity<Resources<PersistentEntityResource>> taskAttributes(
@PathVariable("id") Model model,
@PathVariable("index") int taskIndex,
PersistentEntityResourceAssembler assembler) {
if (model == null) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
if (taskIndex < 0 || taskIndex >= model.tasks.size()) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
}
List<Attribute> attributes = model.tasks.get(taskIndex).attributes;
List<PersistentEntityResource> resources = attributes.stream()
.map(t -> assembler.toResource(t))
.collect(toList());
return ResponseEntity.ok(new Resources(resources));
}
}
这使得对http://localhost:8080/api/models/1
的调用返回如下内容:
{
"name": "myModel",
"tasks": [
{
"name": "task1"
},
{
"name": "task2"
}
],
"_links": {
"self": {
"href": "http://localhost:8080/models/1"
},
"model": {
"href": "http://localhost:8080/models/1{?projection}",
"templated": true
},
"taskAttributes": [
{
"href": "http://localhost:8080/models/1/task/0/attributes"
},
{
"href": "http://localhost:8080/models/1/task/1/attributes"
}
]
}
}
最后,为了让所有这些在UI中更有用,我在模型资源上添加了一个Projection:
@Projection(name = "ui", types = {Model.class, Attribute.class})
public interface ModelUiProjection {
String getName();
List<TaskProjection> getTasks();
public interface TaskProjection {
String getName();
List<AttributeUiProjection> getAttributes();
}
public interface AttributeUiProjection {
String getName();
}
}
这使得人们可以获得属性属性的子集,而无需从&#34; taskAttributes&#34;中获取它们。 REL:
http://localhost:8080/api/models/1?projection=ui
会返回如下内容:
{
"name": "myModel",
"tasks": [
{
"name": "task1",
"attributes": [
{
"name": "attrForTask1",
"_links": {
"self": {
"href": "http://localhost:8080/attributes/1{?projection}",
"templated": true
}
}
}
]
},
{
"name": "task2",
"attributes": [
{
"name": "attrForTask2",
"_links": {
"self": {
"href": "http://localhost:8080/attributes/2{?projection}",
"templated": true
}
}
},
{
"name": "anotherAttrForTask2",
"_links": {
"self": {
"href": "http://localhost:8080/attributes/3{?projection}",
"templated": true
}
}
},
...
]
}
],
"_links": {
"self": {
"href": "http://localhost:8080/models/1"
},
"model": {
"href": "http://localhost:8080/models/1{?projection}",
"templated": true
}
}
}
答案 1 :(得分:0)
您没有任务的存储库 - 在Spring数据中,如果您没有存储库,则没有控制器。我认为如果任务只包含一个属性,你会得到一个链接 - 但你有一个Set - 所以对属性的访问将是任务资源的子资源。
所以你的场景不起作用。我会尝试使用您导出的TaskRepository并删除属性存储库。
然后您的模型资源将包含其任务的链接,任务资源将嵌入属性。
如果您仍希望将任务内联到模型资源中,则可以使用投影。