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?
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:
- Get a
Post
and its collection of Comment
objects.
- Append your new
Comment
to the collection.
- 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.