In my Beyond Frameworks talk, I explained how a component-based architecture can help answer some of the important (i.e. expensive!) questions you might face when creating long-lived apps that rely on a PHP framework. In this series of blog posts, I’m going to look at how to go about creating and working with components.
In the last article, I set out some very simple requirements for my example app. Now that I have an idea of what I want to build, I need to break that down into a set of components to create. My apologies if you’re keen to jump straight to the code; it’s coming soon, and I want to make sure it’s the right code when we get there
Thought Process: Decomposition
Decomposition is all about figuring out how to chop up a large problem into a co-operating sets of smaller problems. If you get the boundaries right across the set, then you can solve each of the smaller problems on their own, glue it all together, and job done.
In a traditional PHP app, decomposition normally means getting into class design. When you’re working with components, you need to decide how you’re going to group those classes together into components.
Figuring out your list of components and their contents very much a creative process. I’m a visual person, so I do it by scribbling on whiteboards or moving things around in OmniGraffle. There are few right or wrong solutions – most ‘wrong’ solutions are really just philosophically differences between people – but there are a few important questions that I ask myself to avoid problems later down the road.
Questions When Decomposing To Components
When I’m deciding what should be grouped into components, I’m always asking myself these questions:
- Does the component do one job, and do it well?
This is the key question. Just like classes that have too many responsibilities become too complex to both use and maintain, the exact same trap lies in wait when you start designing components, just on a larger scale.
Take a look at the components that phix is made up of, by reading its package.xml file. Each of those components does one job, and only one job. phix glues them all together, but you could take any one of those components, and use it to solve the same problem in a different app.
- Is it clear which components relies on which?
A good feel-good factor for deciding whether you’ve got the right components or not is how easy it is to compile a list of which components your app relies on, and which components in turn they rely on. If you feel any doubt or uncertainty, then listen to your heart, and carve up the code into a different set of components.
Ultimately, you need to be able to fill out your component’s package.xml file with a definitive list. Take a look at ComponentManager’s package.xml file as an example. It clearly states every component that ComponentManager glues together.
- Do two components mutually depend upon each other?
One of the key benefits of creating components is being able to isolate change. If I change Component A, I might have to change Component B too, because I’ve broken backwards compatibility. That doesn’t cause any architectural problems. Now, if I change Component B, and then have to change Component A too, then that is a very important problem. You have to install components one at a time. Which one do you install first?
This problem can also appear in more subtle ways, when you have three or more components that rely on each other, for example when Component A relies on Component B, Component B relies on Component C, and then Component C relies on Component A.
This is an easy one to work out. Draw out the list of components as a graph, connecting up which components rely on which. If you end up with a tree, you’re fine. If your graph has cycles, you’ve got a problem that needs to be fixed. You can’t reliably install components with mutual dependencies.
- Is there any shared code I can split out into its own component?
One way to solve the interdependence problem between Components A and B is to move some of the functionality out into a Component C, and then have both Components A and B rely on that, instead of each other. This is also one way of achieving DRY: Don’t Repeat Yourself.
- How is someone going to install and upgrade this component?
What I’m asking here is: will the component be listed in the <dependencies> section of a component’s package.xml (or an app’s!), or will a user install the component for himself by running the pear command? Or maybe both?
Sometimes, you’ll want to introduce a meta-component into your design to make it very easy to install and upgrade a larger collection of components.
ComponentManager is a meta-package; all it does is pull in all the other components that support creating and managing specific types of components. Every time I find and fix a bug, or add a new feature, the bug fix or feature goes into one of the specific components (such as ComponentManagerPhpLibrary) … but you don’t need to keep track of them all. All you have to do is pear upgrade phix/ComponentManager, and it takes care of upgrading all of the other components for you.
- Where will your component be installed?
Components can be installed into two places on a computer: system-wide (e.g. /usr/share/php), or inside an app (e.g. vendor/ folder inside the app’s code tree). Does the component work in both places?
This is a question I personally wish the Doctrine developers had asked themselves. At the time of writing, Doctrine assumes that it will only be installed system-wide, making it a real pain in the backside if I want to run two or more separate apps that use Doctrine on the same server. By forcing me to install Doctrine system-wide, they also force me to test (and potentially fix) all apps that use it in one go whenever I need to upgrade Doctrine.
The components built using ComponentManager can easily support installing into both system-wide or inside an app to suit your choices, and I’ll show you exactly how that is done when we get there.
- Can I get this functionality from an existing component from someone else?
Or, put another way … am I re-inventing the wheel here? Or living my not-invented-here dreams?
Today, PHP components are at an embryonic state compared to our cousin languages. There aren’t all that many out there, and support for the PSR0 autoloading standard is still to become widespread.
That said, you should still take a look first before deciding to build your own component. Folks like the Symfony 2 community seriously get components, and are doing a great job in publishing high-quality code that might just suit your needs.
- Am I ever going to re-use this code?
I’ve saved the most important question for last. Is it worth the trouble? There isn’t much practically in creating a component for the sake of it. If you can’t see yourself (or anyone else) ever re-using the code, put the code inside another component (or your app) for now. You can always break it out into a component at a later date.
This is exactly what I did with phix. It started off as a single PEAR-compatible package, and as it evolved, I extracted out more and more code into separate PEAR-compatible packages when I felt it was worth doing so.
These are all questions about how to organise the code, and it’s this organisation – this list of components and their dependency graph – that then affects the design of the code inside each component. It’s good to start out with a plan, but don’t get hung up on it. As you build the code, and learn to feel what it’s like to live with the list of components, you’ll probably want to make changes to make life easier. There’s nothing at all wrong with that.
So what could the component list for my example app look like?
Component Design For The Sentiment App
If I apply these questions to the app I’m building in these blog posts, I end up with:
Components by type …
- The orange components are going to contain useful code. Either I’m going to have to create them (sental, sentalXmlStore, repustateApi, lymbixApi, rssFeeder) or they already exist (phix, phix’s dependencies).
- The green components are meta-packages, to make it easy to manage creating and updating the code (sentalApis, sentalFeeders).
- The blue component contains shared code (sentalApiShared). It’s going to contain the interfaces that all the APIs will have to implement, and will also hold any base classes or utilities that the APIs are likely to find useful.
I’ve decomposed my requirements from this morning into a list of named components to tackle. Whether it’s the right list or not is too early to tell – there’s always more than one design that will work – but this is what I’m going with for now.
For the rest of this week’s blog posts, I’ll focus on creating the repustateApi component. Code at last And that should just about (fingers crossed!) give me enough time at the weekend to fill in some of the other components before we pick things up again on Monday