I know I'm late with this, but I've only recently really been discovering the podcast as a way of keeping informed. My daily commute is quite long because of traffic jams and a good way to keep yourself occupied with something useful is to listen to podcasts.
On one of my recent commutes I started listening to the Sound of Symfony podcast. As I had just discovered that one, I decided to listen to their most recent episode, which is on best practices for bundles. I quite disagreed with what was being said in the podcast. I started voicing my disagreement on Twitter but quickly decided that 140 characters is not enough to really explain my disagreement. So here's a blogpost.
The Best Practices documentation
First of all, I need to say something about the Symfony best practices documentation. At some point the symfony project released an "official best practices book", and in the podcast they refer quite a bit to this book. The book is being referred to as "this is how you're supposed to be doing Symfony" and "In 95% of the cases, this is how you do it". Let me state that I sincerely disagree with this. I think the practices described in the Symfony best practices book are not really best practices, but instead they are a good starting point. In my opinion, the Symfony best practices should've been called the "Getting started with Symfony" book.
I also disagree with the fact that you should be setting up all your Symfony projects in the same way "because it's easier to understand for new developers". If you do a lot of similar projects it makes sense to set up your projects the same way, but it really depends on the project. Look at the project, not the developers, to determine how you set up your Symfony installation. Code organization is, to me, much more important than how long the on-boarding process for new developers is. On-boarding is a one-time investment in a project. A good code structure will help you in the long run as well.
All Symfony documentation (and also the Sound of Symfony podcast) assume your project contains a single bundle: The AppBundle. This bundle is supposed to contain all your application code. ALL your application code. When you're working on a very small application, this perhaps makes sense, but just about any project I've ever worked on, this is not realistic. There's a lot of code in most applications. Also, putting it all in a single bundle makes it very unstructured and bloated. I personally really dislike that.
While I was at ZendCon this year, I attended the keynote by Robert C. Martin. His keynote went into a lot of different programming best practices, and one thing really resonated with me: Code structure.
At some point during the keynote the slide contained a directory structure of a project. It was pretty clear from the directory structure that this was a project built on a specific framework, but looking at that structure, there was no way to see what the project was actually about. One of the statements Uncle Bob made was that the structure of your code should not show too much about your framework of choice, but should communicate to you what the code is actually doing.
Structure in a Symfony project
Now, the basic directory structure for a Symfony project will always show you that it is a Symfony project. However, you could argue that the project root is a basic filesystem, and the directories that a Symfony project have reflect the purpose of the underlying directories.
app/ will contain application-specific stuff,
web/ is your document root, and
src/ will contain your source code. As such, I think it's fair to apply the lesson Uncle Bob was teaching specifically to the
src/ directory. If that directory only has an
AppBundle directory, you have no idea what the project is about. The only thing you know is that you're looking at the code for an application. Well, duh.
Bundles to structure your code
So the first thing I'd suggest is to use bundles in Symfony to add a bit more structure to your code. Name them after what it does and your code is already a lot easier to understand. If you have a webshop, you could have a
CartBundle and a
CheckoutBundle for instance. If you build a CMS-based website, having an
PageBundle and perhaps a
ForumBundle makes sense. I can take a look at your
src directory and immediately see what goes where.
What about your domain code?
Now there is a slight issue with this approach. Because what do you do with your domain code? The code that contains your business logic, that is not tied to your framework but to your business. Because we don't want to tie our business logic to the framework, putting the domain code into the bundle feels wrong. We want to have it seperated. Since the
src/ directory is the place to put all custom code, this would mean we'd also have to put that in the
src/ directory. Following that logic, we'd now have the following directory structure:
While not necessarily wrong, this feels wrong. This feels like duplicating things, or splitting things that somehow belong together. There's a solution for that.
Adding more structure to your project
In a project I recently worked on I was introduced to the concept of Bounded Contexts. The structure of this project was really nice, because we wouldn't even leak the framework implementation in the first level of the
src/ directory. Taking the webshop example I had because, the
src/ directory would look something like this:
If you're new to this project, you'd immediately understand that this is a webshop, and that it has code in there for a cart, the checkout process and products.
Now, within every directory, the same structure could be found:
Bridge/ directory and namespace would contain code that would connect to external systems. It provides the bridge between our domain objects and the storage/persistence systems we were using. This code is not part of our domain code, but not really part of our application either.
Bundle/ directory and namespace would contain our actual Symfony bundle. It contains all the code that one would expect in a bundle: Controllers, configuration, templates and any other code required to tie our domain code to our framework implementation.
Of course, the
Domain/ directory contains all our domain code. This may contain interfaces for certain code, domain objects (usually POPO's that reflect data structure), specific exceptions for our code, etc.
This directory will contain all the tests that we've written. This could be unit tests, but also integration tests. Anything that tests our code will be here.
The importance of structure
As a consultant and freelance developer I go into a lot of companies. I either develop with an existing team, make changes to an existing (and sometimes unmaintained) codebase, I do code reviews or help in improving a codebase. I agree with everyone who says that having a common structure in a codebase will help with getting to know the codebase, and as such I understand people going for the standard structure that a framework provides. While I prefer Symfony in many situations, I am not tied to a single framework. Over the past two years I've worked on projects built on top of Symfony, Zend Framework, Laravel, Slim, Code Igniter and Silex. I've worked on projects that adhere to their framework default structure and also on projects that have a custom structure (such as the structure above). For me, it is a lot easier when the structure reflects what the application is about instead of what tool it is built on top of. I am therefore extremely happy to have worked on a project with the above structure. It has taught me a new way of structuring code that makes more sense than anything I've done before. It was a good confirmation to hear Uncle Bob describe a similar structure in his keynote at ZendCon.
I can understand if you opt to go for the default structure of the framework you're working with, but perhaps it is good to think a bit longer about it. While the initial learning curve may be a bit more steep when you go for another structure, it may actually make things easier in the long run. And if you document your choices well, everyone will have a place to check for reference.
Keep on learning
I've said it before and I'll say it again: It is important to keep on learning. One of the things I've learned this year is a new way of structuring code, which is part of the Domain-Driven Design approach. I've been learning about DDD a lot this year and I already realize that I'll need to learn even more about this. If the concepts in this blogpost are new to you, then you probably want to learn about this as well.