2013-06-20 78 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類和域類之間進行轉換。實際上,這些方法也有助於可維護性 - 當發生變化時,您可以很容易地看到對另一個對象有影響。