A while ago I posted an entry about version numbering and ended it with two questions:
- When is backward compatibility broken? This probably looks like a stupid question, but imagine: A DLL named “SomeFunctionality.dll” uses some other DLL’s, named “Other.dll” and “MoreOther.dll”. But then this ”SomeFunctionality.dll” is changed and doesn’t use “Other.dll” anymore, but uses “Another.dll”. However, the interface of “SomeFunctionality.dll” didn’t change. Is backward compatibility broken or not?
- What about customer related changes to old software which breaks backward compatibility? Say for example you have version 126.96.36.199 at a customer site. Meanwhile version 2.x.y.z has already been released. Now the customer with version 188.8.131.52 wants some new feature which breaks backward compatibility. However, major version 2 is already taken. What major version do you use?
After some thought I will present you today with possible solutions.
Solution for backward compatibility
The wikipedia provides us with a definition for backward compatibility. Although there is no explicit answer to our question I think it is fair to say that backward compatibility is not broken. Thus we do not change the major part of the version number of the DLL named “SomeFunctionality.dll”.
Solution for customer related changes
To avoid breaking changes you should try to design your software that changes do not break it (that’s obvious !!). This one is more difficult as it is related to your software being extensible: can you change and extend parts of your software without breaking it.
If a client uses a certain implementation, how can I change that implementation without having to change the client?
Because that is our fundamental question here. Remember we are talking about version numbering and version numbering changes bubble from the bottom to the top. A DLL or library changes it’s implementation and if this breaks backward compatibility, then each client using this library is broken. Unless you can make sure your client can handle the old and new situation. Being able to do this requires careful design.
With this, we enter the paradigms of abstractions, design patterns, inversion of control, etc…
A good object oriented design has a good abstraction and this provides the possibility for replacing some high level functionality by another implementation.
Take for example the problem of saving data.
If you have a class Database with methods SaveToDatabase and RetrieveFromDatabase, you can not replace it by a class File with methods SaveToFile and RetrieveFromFile.
If you instead have an interface Archive with methods Save and Retrieve and your client code uses this interface, you can substitute the implementing class by any class implementing the same interface.
This requires you to think about what it is you are trying to achieve in your program and find the proper abstractions for it, and a consice naming of the methods.
I will not repeat all known design patterns here, but following examples come to mind:
- Abstract Factory and Factory Method: a factory class or method enables the creation of other classes implementing a certain interface. If you replace the factory with another one, you can produce other classes implementing the desired interfaces in a different way.
- Adapter: match the interfaces of classes. If you have a class providing a certain functionality by a certain interface, you can replace that class with another one and write an adapter on order to emulate the previous interface.
There are of course a lot more design patterns that can be used to provide extension points to your application.
Inversion Of Control
Although the above does make the base for being able to substitute blocks of functionality by different implementations, you still compile the changes in your application. You must somehow be able to tell your client to use the new implementation. This can be done with inversion of control.
Inversion of control containers allow you to describe the interconnection between your services. By changing the description, you change the interconnection. Although not a hard requirement for inversion of control, mostly the description of the interaction is in the form of some configuration file, like an xml file. So by simply changing the description file, you can rewire your application.
The solution for backward compatibility is easy: it’s more like a convention.
The solution for customer related changes is much more difficult. Although, as shown above, applying some common design paradigms can get you a long way one problem still persists: how can you know where to put your extension points into your program? It takes experience and knowing your customers to answer that question. And then there is of course the KISS (Keep It Simple Stupid) and YAGNI (You Ain’t Gonna Need It) rules of programming.
And you thought versioning was simple…