In discussing application architecture we need to start with an understanding of what we are looking for from the architecture. Architecture is additional work over and above simply writing code. We therefore need to understand the justification for doing it. Additionally if we wish to do it well we need to understand our goals for the architecture so that we can best meet them.
All applications have at least an implicit architecture, even if this architecture can be best described as a "towering pile of hacks". Therefore we must consider what thinking about architecture explicitly gives us over this base condition. The comparison application I will use in this post is (unfortunately) typical of many applications that I have encountered. It has a number of characteristics typical to applications built by junior or unskilled developers. This includes:
- No distinction or separation between types of logic (e.g. business, presentation or data access)
- Duplicate logic common throughout the application, often with subtle variations
- Direct use of relational data in business logic (i.e. no domain model)
Before I offend anyone too much I'll point out that architecture is something that we learn through experience and the architecture of some of my early efforts is not something that I would consider acceptable today. It's unreasonable to expect that junior developers be able to create perfect application architectures (and not only because there is no perfect application architecture).
Having an architecture for your application is a key part of the application being understandable. In all but the most trivial applications there are too many concepts to be considered simultaneously. Good architecture allows concepts to be captured and contained within particular areas of responsibility. This reduces the number of concepts that need to be dealt with while reasoning about the architecture. Reducing the number of concepts, especially those that are extraneous, makes it easier for limited human minds to deal with the problem.
Maintainability is the property I look for above all in application architecture. Maintainability is a measure of how easily and effectively an application may be modified to correct defects or to alter its behaviour or adapt to new environments. Maintainability is heavily influenced by the level of understandability of the application. Additionally factors such as cohesion and coupling and the amount of duplication of logic will also be highly significant in determining if an architecture may be considered maintainable. Maintainability will directly impact the cost of maintaining your application hence improved maintainability will significantly the effect the total cost of the application over its lifetime.
I consider maintainability to be the single most desirable property of an architecture or application because in the long term it is the best way to ensure all the other properties are achievable. It is significantly easier to modify a maintainable system to achieve the desired level of (for example) performance than it is to take a system with high performance and make it maintainable. This is generally true across all the properties of a system. Optimising for a particular property other than maintainability will result in significant costs in the event that other factors become more significant over time that at the time of design and will also directly impact the cost of resolving the inevitable bugs in the system.
The functionality that is desired from an application changes over time. This is due to a number of reasons. Time and budget constraints limit what can be delivered in a version of the system. Choices must therefore be made from all the desirable functionality as to what is to be delivered in a version. This generally implies that there is additional functionality that is desired that may be requested in future versions. Additionally it is often the case that new possibilities are identified once a version has been delivered, either because the version has a limitation in dealing with a business function or because the functionality it provides highlights other functions that would be beneficial to the business that were not previously considered. An architecture that is extensible will allow for this additional functionality to be added without having to perform significant rework of existing unrelated functionality. This is not only a cost saving in terms of effort but reduces the risk of introducing errors into existing functionality.
Technology and business conditions change over time. Architecture that is flexible allows the application to be adapted to these changes in an effective manner. This will include such features as isolating the application environment from the core business logic such that the environment may be altered without having to modify the business logic. Flexibility is a related concept to extensibility that also affects the ability to maintain the system in a cost effective manner.
It is obviously highly desirable that an application be reliable. If users cannot have confidence that the application will produce correct results or that it will be able to perform the function for which it is intended then the utility of the application will be severely compromised. Unreliable applications may have a negative return on investment due to additional costs to verify and correct their behaviour. In some cases failure of an application may have consequences beyond additional costs, leading to legal concerns or injury to individuals or groups. Architecture supports producing reliable applications in a number of ways, including making the application more testable and reducing the chance of errors by decreasing complexity and improved understandability.
We live in an imperfect world and applications need to be able to deal with these imperfections. The ability of an application to deal with less than perfect conditions is known as robustness. This measures how well an application continues to perform in the event of bad data or failures in its dependencies. Applications display high robustness if the failure of other elements of a system do not cause the application to also fail.
It should be noted that there are cases where the appropriate behaviour for an application is to terminate. If the application state becomes corrupted then an application has a number of choices. It may choose to continue regardless. This is generally bad as the behaviour from this point is essentially undefined and the result will likely be incorrect. Many applications take this path by not performing error handling. An application may choose to attempt to repair its state in order to continue working. Unfortunately in many cases it will not be possible to completely determine what state is affected or how best to recover it. This means repair attempts are at significant risk of not properly addressing the failure, and the application will continue to perform incorrectly. Hence the final choice, which is to exit with an error notification, is generally the best option. This ensures a number of things. Firstly users are notified that a problem occurred, addressing the risk of them using an incorrect or incomplete output. Secondly the presence of an error is explicitly identified and the root cause of the issue may be corrected. This is ultimately the preferred solution as it eliminates the underlying cause rather than treating symptoms and hoping that all manifestations of the error are addressed.
I'm always reluctant to discuss performance as a requirement. Not because it's not important but because it's given importance than can be justified. In almost all applications there is a "good enough" level of performance where effort expended for additional performance is essentially wasted as the additional performance is unnecessary. For many applications this "good enough" level is surprisingly low. Most applications will have a few hotspots where performance is required but in the general case it is not a large concern unless the system is particularly inefficient. It is well established that developers are particularly terrible at determining what these hotspots are until the application can be measured. As such early optimisation is generally wasted effort and its negative impact on maintainability makes it undesirable. The appropriate way to architect for performance is therefore to design for maintainability and optimise as necessary to meet the specified performance goals.
There are of course some specific performance concerns that should inform an architecture. General rules such as avoiding chatty interfaces when performing communication should not be ignored simply to avoid addressing performance concerns too early. However a focus on the absolute performance of every element of your architecture is generally unwarranted.
In summary we want our architecture to be:
- Understandable
- Maintainable
- Extensible
- Flexible
- Reliable
- Robust
- Performant as needed
In future posts I will be considering some example architectures and how they address these criteria.