CQRS: How to handle file uploads?

If you are like me one who tries to keep up with all the cool stuff happening in the PHP world you’ve probably noticed the buzz around Domain Driven Design and more recently Event Sourcing and CQRS. Last year Qandidate released Broadway: a project providing infrastructure and helpers for introducing CQRS and Event Sourcing into your PHP stack. It wasn’t until last month before I got the change to get my hands on it. We adopted the framework in one of the latest projects at work. And it didn’t take long before we ran into all kind of problems and questions 🙂 .

So for every question we have I’ll try to write a blogpost so others can learn. Also I’m curious about how you handle the problems I describe in these posts, so don’t hesitate to comment if you have a different opinion. Let’s dive into what should be the first in a series of post about CQRS!

The problem

In the application we’re building one requirement is that users can configure attachments to be send to a user when performing some kind of action. We’re using Symfony2 and Broadway so I our code will be very specific to these frameworks. Consider the following form:

In the controller we validate the form, construct our UploadAttachment command – which is just a DTO – by passing all the values from to form to the command bus:

And the command handler calls the appropriate method on our aggregate:

Our aggregate creates a new event:

But as you probably noticed now we run into problems because we’re passing around a UploadedFile instance in an event. Imagine how this would get stored into the event store:

Storing the complete file in the event storage is theoretically possible but we prefer to store our files not in MySQL but somewhere in a S3 bucket in the cloud. If you do your event store will grow quickly and you’ll have other challenges to wrap your head around. Keep in mind events often will be transferred by some queue like RabbitMQ.

After some digging around on the internet I found some others with the same problem. On Freenode #qandidate I also asked for advice. In general everybody stores the file in the controller or command handler and passes on the id to the event.

The solution

We’ve chosen to store the file in our controller and pass on the UUID to the command. A code example is worth a thousand words:

Drawbacks

There are a couple of drawbacks in this method:

  1. every new attachment results in a new file, this could take up a lot of storage from unused files
  2. if something goes wrong in the command handler, the file is stored already

Personally I see it as a benefit we have a history of every single attachment uploaded. We can easily go back in time and revert an erroneous upload or debug what our users did wrong in case of a problem.

By only passing around the UUID our event keeps small and this makes it easy to be published on RabbitMQ.