That's, what links are for:
E.g. requesting the first post via GET http://example.com/posts/1
{
"id": "1",
"author": {
"nickname": "me"
},
"links": [
{
"rel": "self",
"href": "http://example.com/posts/1"
},
{
"rel": "author",
"href": "http://example.com/authors/me"
}
]
}
The response contains a link to the author (named me). If you want to retrieve all of me's posts, you would do a GET to http://example.com/posts?author=me
; and if you want it paginated, you would add a queryparameter like http://example.com/posts?author=me&page=1
.
Say, you have collaborations between various authors, the initial answer would've been:
{
"id": "1",
"authors": [
{
"nickname": "me"
},
{
"nickname": "mo"
},
{
"nickname": "ma"
}
],
"links": [
{
"rel": "self",
"href": "http://example.com/posts/1"
},
{
"rel": "author",
"href": "http://example.com/authors/me"
},
{
"rel": "author",
"href": "http://example.com/authors/mo"
},
{
"rel": "author",
"href": "http://example.com/authors/ma"
}
]
}
On the other hand, under the links-section of the author, there should only be a reference to the latest post (to prevent too much data in there).
If you want to get a set of all collaborative posts from the trio above, you would query as follows:
GET http://example.com/posts?author=mo,me,ma
If you want all posts - including the collaborative - from the trio, you would query like this:
GET http://example.com/posts?author=mo|me|ma
This is a clean and straightforward approach.
To express, that something is related, you use links. If you want to filter you do it via querystring.
I've had the same problem and "solved" it by modelling REST resources differently, e.g.:
/users/1 (contains basic user attributes)
/users/1/email
/users/1/activation
/users/1/address
So I've basically split the larger, complex resource into several smaller ones. Each of these contain somewhat cohesive group of attributes of the original resource which is expected to be processed together.
Each operation on these resources is atomic, even though it may be implemented using several service methods - at least in Spring/Java EE it's not a problem to create larger transaction from several methods which were originally intended to have their own transaction (using REQUIRED transaction propagation). You often still need to do extra validation for this special resource, but it's still quite manageable since the attributes are (supposed to be) cohesive.
This is also good for HATEOAS approach, because your more fine-grained resources convey more information on what you can do with them (instead of having this logic on both client and server because it can't be easily represented in resources).
It's not perfect of course - if UIs is not modelled with these resources in mind (especially data-oriented UIs), it can create some problems - e.g. UI presents big form of all attributes of given resources (and its subresources) and allows you to edit them all and save them at once - this creates illusion of atomicity even though client must call several resource operations (which are themselves atomic but the whole sequence is not atomic).
Also, this split of resources is sometimes not easy or obvious. I do this mainly on resources with complex behaviors/life cycles to manage its complexity.
Best Answer
I will try to answer with the info you gave me.
You wrote
So I think that your approach is too CRUD for DDD and aggregate roots. My 2 cents your business method is something like Update(Book) and the parameter is the modified Book (its data, isbn or title for example, and/or its author collection) and that is why you have to "detect" changes in the aggregate root and why you are in trouble about how to do that.
I would implement a command pattern and segregate every user-case into a command.
This will need a good task-based UI design.
Then, you can choose the most suitable strategy:
Execute every command instantly and save into persistence. Get Book aggregate root, modify book data or author collection (depends of the command) and update persistence. No need of tracking changes.
Execute every command instantly in the aggregate root in memory and wait user push "Save" button. Then go to persistence. If you store the commands executed in the aggregate until persistence is updated you already know what update in persistence. Changes tracked by the commands executed in the aggregate.
Store a list of generated commands by the user and execute it in a batch when user push "Save"; use (1) or (2) strategy to update aggregate and persistence.
Last words and tips:
Even UI design affects the architecture and patterns you can use. Think in everything as a whole.
Every strategy listed above has its uses. Depends of your domain, context, environment, technologies you use, etc.
You can use several strategies. No need to stick just in one in every bounded context.
Your domain has to model user-case actions. Don't do just Update(Book). Model every action (every command execution) in its own function and apply its relevant domain rules and invariants.