Snowflakes and Architecture - Layers considered harmful - Steve Love at ACCU 2008
Steve Love’s session at the ACCU Conference was billed as taking a “suspicious look at the traditional layered architecture, and suggest[ing] some ways it can be improved upon, resulting in an “architecture” that resembles a snowflake more than it does a cake.” Attending his talk fitted in with my plan to focus more on the architecture sessions on Day 1 of the conference.
Steve was straight in with the boot beginning by noting that the main difficulty with layer diagrams is that the diagrams are often only what you get. (More marketechture than architecture)
Layers are created to separate concerns but often business logic leaks into different layers (more on this when I blog about my ‘When Good Architecture Goes Bad’ session) – this leads to untestable code due to unintended dependencies between layers e.g. the business application that must be tested with a GUI. Another problem is that sometimes a layer is there only to pass data through to lower layers.
Steve’s talk described a different (granular) approach to making a more adaptable architecture by design and looked at the mutual influence of architecture and design.
Robert Martin notes that bad design is rigid, fragile (knock-on effects of local changes) and immobile (can’t move things around for reuse, can’t disentangle software). Booch (1996) suggests that instead of layering, we arrange applications into smaller granule components that communicate together. So good design is cohesive, decoupled & layered. But Steve questioned whether these are enough…
What a user sees as quality is different from what developer sees as quality. (MD - In fact there is often a similar disconnect between different groups within a development organisation and this can also lead to problems.) His claim is that simplicity in software architecture is key, is easy to measure, but is not straightforward to achieve.
To illustrate what he meant Steve introduced concept of Dependency Horizon that is the number of steps you need to bring in for a particular dependency (think of it as the number of steps between components, packages or modules if you will). A far dependency horizon can become hard to manage and can increase chance of circular dependencies (not good).
Decoupling (e.g. through intermediate objects or interfaces) improves maintainability. This can be done by identifying abstractions and pulling out pure interfaces. This enables the dependency horizon to be shortened and so maintainability is improved.
A key concept here is the Dependency Inversion Principle
- High level modules should not depend upon low level modules. Both should depend upon abstractions.
- Abstractions should not depend upon details. Details should depend upon abstractions.
- This allows abstractions to depend on each other, but not on their implementations.
DIP is layering in the small.
The Singleton pattern came in for a lot of bashing in Steve’s talk. Singleton is the counter-example of DIP – it is the antithesis of detail depending on abstraction. The pattern has lots of problems – hard-wired dependency, dependency from within, testing is difficult, rigid/fragile/immobile (see above), unpredictable ownership, unpredictable lifetime, handling when multiple threads present. Conclusion – Singleton pretty much violates all of Martin’s design principles.
Steve’s solution is Unsingleton (MD - or perhaps antiSingleton) – code should work with what it wants, don’t let it take it for themselves. What client code doesn’t know about the implementation of a service can’t hurt it.
Steve’s solution to many of the problems was to Design to an Interface. This can be realised in different ways in different technologies:
- Interface keyword in some languages
- COM/CORBA - IDL
- C++ Pure Virtual Base Class
- Duck-Typing (ducking the whole idea of typing) - C++ Templates, Ruby, Python
- C# and Java Generics
The key thing about interfaces is their substitutability (Liskov substitution principle).
- Inheritance is the tightest form of coupling possible. Inheritance – polymorphism by dynamic despatch (the conventional model) – virtual functions (AKA the inclusion model of polymorphism).
- Genericity – polymorphism by generics – (Duck typing) – Containers, Iterators and algorithms (strategy), Traits and Policy classes is better.
- Overload – polymorphism by overloaded functions – member function overloading, global functions and operators. Can be particularly powerful in creating interfaces. (MD – this resonated with my experience in Common Lisp.)
- Cohersion – polymorphism by conversion, implicit casting, constness. (MD – again resonated with my Common Lisp experience.)
Testability is an essential property. (MD - A quick aside - When I first started programming I didn’t think about this very much - we weren’t taught it and nobody around me considered it as a property that could be designed in. As I read more about software development I began to take more notice of this and began to look for testability in design documents. Debuggability is a related concept that supports testability e.g. I built a logging add-on for some quite complex database code I inherited and this really let me get to the bottom of some quite complex client-server interaction problems.)
Testability principles:
- You must be able to Test independent parts independently
- Interfaces = Substitutability – if software under test depends on database etc. then change it so that it must depend on interfaces (to the database), this lets you mock (or replace) the DB
- Substitutability underpins Mockability (Mock objects). Gives you ability to mock out services that are only available on target device (pda, phone) and do debugging on PC (for example).
- Design to an interfaces
Parallel Development
- Working to interfaces enables parallel development, continuous integration, testing (again)
- Also supports outsourced development
- Adaptability – when using interfaces plugging in a new component just like an existing one is trivial. (MD – I did have a question in my mind about how much effort and knowledge is required in order to develop these interfaces. Does one need to have written the interfaces a few times in order to get them right?)
Flexibility, Generality and Reuse
- The false idols of OO?
- Usually done with inheritance but these led to big class hierarchies which were unusable rather than reusable. (I touched on this in my session too).
- Interfaces provide the means of reuse. - Component architecture provides the means of flexibility – make stuff talk to a wire feed rather than the UI (say)
- And Generality - Can be reused in different context
Fat interfaces are less useful than small interfaces since they bring in unwanted dependencies. Must design them according to what client code wants, not what it can use. Small, specific interfaces allow better reuse.
Dependency Injection – promises the idea of true decoupling. Spring etc. sometimes a good idea – but sometimes a sledgehammer to crack a nut. Picked up by people wanting to decouple things but they often miss lighter-weight solutions. SOA does give decoupled design but needs thought in Steve’s experience.
Parameterize-from-above is the solution. (Don’t call us, we’ll call you). All about separation of concerns. But it still requires programmer skill to put it all together (MD - what software doesn’t?).
Alistair Cockburn propose Hexagonal architectures – these promote the idea of the application as a service. (With ports and adapters). Port defines contract for adapters – this leads to pluggability. (MD – are interface technologies well-enough defined to support this in the general form? How much effort is required to create these entities?)
Making adapters have their own ports leads to software as a collaborating set of components. Steve notes it’s an attractive design but that it requires discipline and can lead to circular dependencies. At the back of his mind he did seem to be worried about the danger of replacing spaghetti code with spaghetti interfaces. One suggestion was to try and organise interfaces into layers to help. The key here he suggested is to break layering down into its constituent interfaces. So that we still layer components but we don’t make them depend on being in a layer. This allows decoupling within layers.
So - what about the snowflakes? Well, these are pretty hard to describe without a picture, which I don’t yet have. Look out for Steve’s slides on the conference web site adn you’l get the picture (pun intended).