2013-06-20 34 views
4

我将我的节点表示为域实体。我想通过REST接口公开我的节点,使用GET按id查询,POST以保存节点及其相关节点,并通过PUT更新节点及其相关节点。 GET工作得很好,但我有一个关于POST和PUT情况的实现问题。让我来说明这个问题,因为代码经常说的不仅仅是单词。用于暴露Neo4j节点的REST接口实现

在这个例子中,有两个相关节点类型表示为域实体。协作可以有多个标签,标签可以属于多个协作。所以我们有很多关系。它们都共享相同的基类NodeBacked ,它基本上充当底层节点的包装。

NodeBacked

abstract class NodeBacked { 

    private Node node; 

    public NodeBacked(final Node node) { 
     this.node = node; 
    } 

    public Long getId() { 
     return this.node.getId(); 
    } 

    @Override 
    public int hashCode() { 
     return this.node.hashCode(); 
    } 

    @JsonIgnore 
    public Node getNode() { 
     return this.node; 
    } 

} 

协作

public class Collaboration extends NodeBacked { 

    public Collaboration(final Node node) { 
     super(node); 
    } 

    // Leaving out some properties for clearness 

    @JsonProperty(NAME_JSON) 
    public String getName() { 
     return (String) getNode().getProperty(NAME); 
    } 

    @JsonProperty(TAGS_JSON) 
    public Iterable<Tag> getTags() { 
     return new IterableWrapper<Tag, Path>(Traversal.description().breadthFirst() 
       .relationships(Relationships.HAS, Direction.OUTGOING).uniqueness(Uniqueness.NODE_GLOBAL) 
       .evaluator(Evaluators.atDepth(1)).evaluator(Evaluators.excludeStartPosition()).traverse(getNode())) 

{ 
      @Override 
      protected Tag underlyingObjectToObject(final Path path) { 
       return new Tag(path.endNode()); 
      } 
     }; 
    } 

    public void setName(final String name) { 
     final Index<Node> index = getNode().getGraphDatabase().index().forNodes(Indexes.NAMES); 
     getNode().setProperty(NAME, name); 
     if (StringUtils.isNotEmpty(getName())) { 
      index.remove(getNode(), NAME, name); 
     } 
     index.add(getNode(), NAME, name); 
    } 

    public void addTag(final Tag tag) { 
     if (!Traversal.description().breadthFirst().relationships(Relationships.HAS, Direction.OUTGOING) 
       .uniqueness(Uniqueness.NODE_GLOBAL).evaluator(Evaluators.atDepth(1)) 
       .evaluator(Evaluators.excludeStartPosition()) 
       .evaluator(Evaluators.includeWhereEndNodeIs(tag.getNode())).traverse(getNode()).iterator().hasNext 

()) { 
      getNode().createRelationshipTo(tag.getNode(), Relationships.HAS); 
     } 
    } 

    @Override 
    public boolean equals(final Object o) { 
     return o instanceof Collaboration && getNode().equals(((Collaboration) o).getNode()); 
    } 

} 

标签

public class Tag extends NodeBacked { 

    public Tag(final Node node) { 
     super(node); 
    } 

    @JsonProperty(NAME_JSON) 
    public String getName() { 
     return (String) getNode().getProperty(NAME); 
    } 

    public void setName(final String name) { 
     final Index<Node> index = getNode().getGraphDatabase().index().forNodes(Indexes.NAMES); 
     getNode().setProperty(NAME, name); 
     if (StringUtils.isNotEmpty(getName())) { 
      index.remove(getNode(), NAME, name); 
     } 
     index.add(getNode(), NAME, name); 
    } 

    @JsonProperty(COLLABORATIONS_JSON) 
    @JsonSerialize(using = SimpleCollaborationSerializer.class) 
    private Iterable<Collaboration> getCollaborations(int depth) { 
     return new IterableWrapper<Collaboration, Path>(Traversal.description().breadthFirst() 
       .relationships(Relationships.HAS, Direction.INCOMING).uniqueness(Uniqueness.NODE_GLOBAL) 
       .evaluator(Evaluators.atDepth(1)).evaluator(Evaluators.excludeStartPosition()).traverse(getNode())) 

{ 
      @Override 
      protected Collaboration underlyingObjectToObject(final Path path) { 
       return new Collaboration(path.endNode()); 
      } 
     }; 
    } 

    @Override 
    public boolean equals(final Object o) { 
     return o instanceof Tag && getNode().equals(((Tag) o).getNode()); 
    } 

} 

我露出Collaboratio n通过REST(Spring 3.2)进行如下操作。 MappingJackson2HttpMessageConverter用于将POJO转换为JSON,反之亦然。

@Controller 
@RequestMapping(value = CollaborationController.CONTEXT_PATH) 
public class CollaborationController { 

    public static final String CONTEXT_PATH = "/collaborations"; 

    @Autowired 
    private GraphDatabaseService db; 

    @Transactional 
    @RequestMapping(value = "/{id}", method = RequestMethod.GET) 
    public @ResponseBody Collaboration getCollaboration(final @PathVariable Long id) { 
     // should use a service layer but doing this for clearness 
     return new Collaboration(db.getNodeById(id)); 
    } 

} 

这个伟大的工程。 getters向节点查询它的属性,并返回一个体面的JSON字符串。一个例子JSON字符串GET /collaborations/1

{ 
    "name" : "Dummy Collaboration", 
    "tags" : [ { 
    "name" : "test", 
    "id" : 3 
    }, { 
    "name" : "dummy", 
    "id" : 2 
    } ], 
    "id" : 1 
} 

那么,有什么问题呢?想象一下,一个JSON的身体,看起来POST请求如下:

{ 
    "name" : "Second collaboration", 
    "tags" : [ { 
    "name" : "tagged" 
    } ] 
} 

的CollaborationController有如下方法来处理POST请求:

@Transactional 
    @RequestMapping(method = RequestMethod.POST, headers = JSON_CONTENT_TYPE) 
    public @ResponseBody ResponseEntity<Collaboration> persist(final @RequestBody Collaboration collaboration, 
      final UriComponentsBuilder builder) { 
     final Collaboration collab = new Collaboration(db.createNode(Labels.COLLAB)); 
     // Problem!! 
     collab.setName(collaboration.getName());   

     final HttpHeaders headers = new HttpHeaders(); 
     headers.setLocation(builder.path(CONTEXT_PATH + "/{id}").buildAndExpand(collab.getId()).toUri()); 
     return new ResponseEntity<Collaboration>(collab, headers, HttpStatus.CREATED); 
    } 

线collab.setName(collaboration.getName());不会起作用,因为协作类做不包含其自己的属性,并使用直接查询底层节点的getter。在这种情况下,没有任何节点可用,因为Collaboration应该由Jackson2通过Spring的MappingJackson2HttpMessageConverter从JSON转换为POJO。有没有任何属性,所以没有什么要设置的...

我寻找到这个问题的干净的解决方案,但还没有找到一个还没有。我可以使用POJO(或VO或......)作为persist方法传入的参数,但是这并不是真正的维护。更改属性需要更新CollaborationVO(POJO)类的Collaboration类。

建议不止欢迎! Spring Data Neo4j主要解决所有这些问题,但我对它的性能不满意。这就是我尝试采用另一种方法的确切原因。

我希望这个解释不够清楚。感谢您的支持!使用

框架:

  • 春天3.2。3.RELEASE
  • 的Neo4j 2.0.0-M3(嵌入式
  • 杰克逊2.2.2
+0

+1有趣而且写得很好的问题。看起来好像你需要将'Collaboration'和'Tag'从它们的底层实现中分离出来,但是你这样做并不需要增加另一个层来维护是非常棘手的。 –

回答

1

你说:

I could use a POJO (or VO or ...) as incoming parameter for the persist method, 
but that's not really maintainable. 

,但我没有看到解决的办法 - 虽然我同意它看起来很丑,应该有更好的方法;但是,我还没有看到一个。

您未讨论的方法的一个方面是它将底层数据库实现公开给客户端。当然,它被序列化为JSON,但是这会阻止客户端将“协作(或标签)”对象放在一起吗? [你的客户端可能是一个JavaScript客户端,所以这不是问题,但是这并不能阻止其他假想的客户端构建你的对象之一。]如果客户端调用getCollaborations()(或getTags()),会发生什么?方法?对我来说,这是两个恶魔中最糟糕的。

我会将您现有的域实体更改为ECollaboration/ETag,然后为Collaboration/Tag创建纯POJO。 E类知道POJO是可以的,但不是另一种方式。因此,E类也可以通过如下方法实现接口:“convert()”和“valueOf()”以在API类和域类之间进行转换。实际上,这些方法也有助于可维护性 - 当发生变化时,您可以很容易地看到对另一个对象有影响。