2015-10-26 5 views
1

Я в процессе оценки Spring REST как сервер для приложения на основе AngularJS. Я быстро смоделировал наш домен как набор агрегатных корней и ударил следующий дизайн блокпост:Обработка ссылок ресурсов в сборе сущностей с Spring Data REST

  • Модель Ресурс имеет
  • Множественных Задачи Entities
  • ссылающееся множественный Attribute Ресурсы

Я ожидал, что HAL _links для атрибутов plac внутри каждого объекта задачи 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.

I ничего не видели в спецификации HAL для обработки подобных случаев или в документации Spring 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

У вас нет хранилища задач - при весеннем хранении данных у вас нет контроллера, если у вас нет репозитория. Я думаю, вы получите ссылку, если задача будет содержать только один атрибут, но у вас есть Set, поэтому доступ к атрибутам будет являться под-ресурсом ресурса задачи.

Так что ваш сценарий просто не работает. Я бы попытался создать TaskRepository, который вы экспортируете и удаляете репозиторий атрибутов.

Тогда ваш ресурс модели будет содержать ссылку на его задачу, а ресурс задачи будет вставлять атрибуты.

Вы можете работать с проекциями, если хотите, чтобы задачи были включены в ваш ресурс модели.

+0

Привет Mathias, спасибо за ваш ответ. На самом деле у моих задач нет репозитория по дизайну. Они не являются ресурсами и могут рассматриваться как объекты ценности. Моя проблема состоит в том, что два внутренних объекта ссылаются на разные наборы одинаковых ресурсов и могут различать соответствующие ссылки. –

+0

Думаю, у меня есть твоя проблема. Если вы хотите, чтобы такая ссылка «http: // localhost: 8080/models/1/tasks/2/attributes» должна быть ресурсом, потому что атрибуты были бы подресурсом задачи и, следовательно, репозиторий. По крайней мере, если вы не хотите настраивать метод контроллера. Вы можете попробовать собственный метод контроллера, который обрабатывает «модели/1/tasks/2/attributes», тогда вы можете попытаться реализовать «ResourceProcessor» и вручную добавить ссылки на этот метод контроллера в ResourceProcessor. Не уверен, что это сработает - но стоит попробовать. –

+0

Да, я пришел к такому же выводу, как и к тому, чтобы реализовать то, что мне нужно, поскольку, похоже, это не так. Я буду экспериментировать и вернуться сюда с моими выводами, спасибо. –

1

Поскольку данные Spring REST не обрабатывают изначально вариант использования, описанный в вопросе, первым шагом является деактивация управления атрибутами задачи и обеспечение их не сериализации по умолчанию.Здесь @RestResource(exported=false) гарантирует, что (необязательная) ссылка не будет автоматически генерироваться для атрибутов «attributes», а @JsonIgnore гарантирует, что атрибуты не будут отображаться по умолчанию.

@Entity 
public class Task { 
    @Id 
    @GeneratedValue 
    public Long id; 

    public String name; 

    @ManyToMany 
    @RestResource(exported = false) 
    @JsonIgnore 
    public List<Attribute> attributes; 
} 

Далее атрибут _links доступен только в корневом каталоге нашего ресурса, так что я решил внедрить новую отн под названием «taskAttributes», который будет иметь несколько значений, по одному для каждой задачи. Для того, чтобы добавить эти ссылки на ресурс, я создал пользовательский 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" 
     } 
    ] 
    } 
} 

Наконец, чтобы сделать все это более пригодными для использования в пользовательском интерфейсе, я добавил проекция на ресурсе Модель:

@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(); 
    } 
} 

который позволяет один получить подмножество свойств атрибута без необходимости извлечения их из «taskAttributes» отн:

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 
    } 
    } 
} 
Смежные вопросы