Symfony2: Bundle structure – bundles as integration layers for libraries
Many of the bundles that are part of the Symfony2 framework and the available third party bundles are effectively integration layers between a library and the framework. For example:
- The OldSoundRabbitMqBundle integrates the php-amqplib library
- The FOSElasticaBundle integrates Elastica
- The LiipImagineBundle integrates Imagine
What is in the bundle then? One common thing are service definitions to integrate them into a Symfony2 app instead of manually creating objects. Alongside this is exposing the configuration of these objects via the Dependency Injection Extension to allow configuration from the application's overall config files.
The libraries in many cases have been written using dependency injection but the definitions that wire them together are part of the bundle. In some cases the libraries are not written using dependency injection but the bundle can still contain the service definitions to integrate them into a Symfony application.
Service definitions are not the only thing that bundles do to integrate libraries there is also twig extensions, twig templates, some have controllers, listeners for events etc.
In some cases these could be further separated into a bridge and a bundle as with the framework itself. For example, twig extensions and doctrine listeners are not Symfony2 specific and could live in a bridge with the bundle handling service definitions with tags to automatically register them in the application. This would allow other non-Symfony2 applications but which used Doctrine or Twig to make use of the templates and listeners.
This technique also suits code within an application, rather than having everything in a bundle it make sense to split out non-framework specific code into a library and have an associated bundle to integrate this back in. By avoiding having framework specific code you open up the possibility of reusing this library in other applications with no or another framework. Even where it is not useful in another application it will aid any future migrations of the app. This way the bundle is dependant on the app but not vice versa making it much easier to move.
This is not to say that no Symfony code should be used in the library, it makes sense to still use the components but avoiding code from the bundles and bridges makes sense as you are then introducing dependencies only on the components used and not on the whole framework. These can of course be specified in the bundle’s composer.json file so that it can be easily installed standalone elsewhere.
The obvious cases for this are where the code being extracted from the bundle into a library is infrastructure code as it is with most third party bundle/library combinations. For example, if there are particular logging requirements for the application not met by existing solutions, such as having to interact with an in-house log aggregation server. By keeping everything related to this in a separate library and bundle it is then available for use in other applications as well as making any future migrations simpler. It also makes it easier to avoid this code becoming dependant on the needs or worse the business logic of the particular application, which should not be the case for this sort of infrastructure code.
What about the business logic that is specific to the application though? Is there any benefit to doing the same with this? It may seem like it will not be any use outside of this application but if it is part of the business then it may well be useful in other applications even is this is not immediately apparent. Even if it is not used in a different application then any future migrations, even if just to a new major version of Symfony rather than a different framework, will be considerably easier if the business logic has no framework dependencies.
Another benefit is the enforced separation this gets us between the application specific logic and the business logic. Even if we never intend to and don't use the business logic outside of this application then making it work outside of the application will help us to create business logic that is decoupled from the application and easier to maintain and extend for it. We will no longer end to making decisions about business logic based on the needs of the framework.
Unlike the infrastructure code where it relatively easier to separate out what is not framework specific it becomes harder in the case of business logic, if we move our entities, repositories etc. out of the bundle how do we work with forms and validation etc.? This is something I will look at over my next few posts.