Basically, an user wants to change its profile picture.
The web server receives a posted image against /user/35435/profile/picture
, so the data needs to be saved, and the LastModification
property of the profile object updated.
Images are not stored locally in the web server, they need to be uploaded somewhere else (ie: cloud storage).
At the moment, every operation is represented by either a Command or a Query. Commands run in an ambient transaction (like a SQL transaction). The image upload operation is not transactional, but a compensating action can be done in case of error (ie: removing the image if the DB operation fails).
On a naive implementation, a command containing both current date and the image data is created. A command handler is executed, it loads the ProfileAggregate
aggregate, and ProfileAggregate.updateProfilePicture(imageUploader, image, currentDate)
is executed passing the imageUploader
domain service as parameter. Inside the method, the image is uploaded and the profile updated. The command handler saves changes in the DB and returns.
I do not like the fact that a transaction is held during the image upload. I do not like either doing non-domain-model operations (like uploading the image) from within an aggregate (even if the aggregate is calling somewhere else).
Should this interaction modeled like two independent commands that are executed in serial fashion, or it is OK to put anything inside an aggregate as long there is no dependencies in concrete implementations.
Your command handler is something that's probably in the "Application Layer" of your architecture. So, considering that, you receive a command and you orchestrate your domain / other services to satisfy it.
That said, I don't really like two different commands implementation as from the client's point of view they're only doing one action. You could create a proxy command (single command) and on its handler generate two commands (one for handling the aggregate, the other to handle the upload) but this approach will make your API/Model less intuitive in the future.
I also don't like bundling the upload part of the operation inside the Profile aggregate as it doesn't seem like one of its responsibilities at all.
What I will suggest here is the following approach:
The Profile aggregate should be responsible for accepting a picture. So, its operation updateProfilePicture will simply evaluate picture metadata (maybe you have a rule that the picture must have a certain size or be validated against some sort of algorithm to try to find nudity, this kind of stuff) and the internal state of target Profile to allow the action to occur and to update its internal state with lastUpdated property or something.
The ImageUploadService will provide you with standalone functionality to receive the image and store it accordingly.
Both these operations will happen independently and will be eventually consistent through messaging.
As such, your command handler will:
When ImageUploadService finished receiving the image, it will fire a message to the Profile domain reporting success/failure of the upload. Then: