Using Spring Data REST to handle complex aggregate

2019-04-09 06:31发布

问题:

Right now I can't get the concept behind Spring Data REST if it comes to complex aggregate roots. If I understand Domain Driven Design correctly (which is AFAIK the base principle for spring data?), you only expose aggregate roots through repositories.

Let's say I have two classes Post and Comment. Both are entities and Post has a @OneToMany List<Comment> comments.

Since Post is obviously the aggregate root I'd like to access it through a PostRepository. If I create @RepositoryRestResource public interface PostRepository extends CrudRepository<Post, Long> REST access to Post works fine.

Now comments is renderd inline and is not exposed as a sub resource like /posts/{post}/comments. This happens only if I introduce a CommentRepository (which I shouldn't do if I want to stick to DDD).

So how do you use Spring Data REST properly with complex domain objects? Let's say you have to check that all comments does not contain more than X characters alltogether. This would clearly be some invariant handled by the Post aggregate root. Where would you place the logic for Post.addComment()? How do you expose other classes as sub resources so I can access /posts/{post}/comments/{comment} without introducing unnecessary repositories?

回答1:

For starters, if there is some constraint on Comment, then I would put that constraint in the constructor call. That way, you don't depend on any external validation frameworks or mechanisms to enforce your requirements. If you are driven to setter-based solutions (such as via Jackson), then you can ALSO put those constraints in the setter.

This way, Post doesn't have to worry about enforcing constraints on Comment.

Additionally, if you use Spring Data REST and only define a PostRepository, since the lifecycle of the comments are jointly linked to the aggregate root Post, the flow should be:

  1. Get a Post and its collection of Comment objects.
  2. Append your new Comment to the collection.
  3. PUT the new Post and its updated collection of Comment objects to that resource.

Worried about collisions? That's what conditional operations are for, using standard HTTP headers. If you add a @Version based attribute to your Post domain object, then every time a given Post is updated with a new Comment, the version will increase.

When you GET the resource, Spring Data REST will include an E-Tag header.

That way, your PUT can be conditionalized with an HTTP If-Match: <etag> header. If someone else has updated the entity, you'll get back a 412 Status code, indicating you should refresh and try again.

NOTE: These conditional operations work for PUT, PATCH, and DELETE calls.