The role of software architects is to engage in programming tasks while guiding the team toward a design maximising productivity
Architecture shapes a software system through component division, arrangement and communication
Facilitates development, deployment, operation and maintenance
The strategy is to leave as many options open for as long as possible
Architecture has little to do with whether a system works properly
There are many systems out there running with terrible architecture
More of a passive and cosmetic role, rather than active or essential
Goals of architecture:
Enhances understanding, development, maintenance and deployment
Aims to minimise the lifetime cost of the system and maximise programmer productivity
Development
A system that's hard to develop risks a short lifespan
Team structure impact:
Small teams: A team of five can effectively create a monolithic system without strict components or interfaces, as architecture may impede early development
Large teams: A system developed by multiple teams requires clear component division and stable interfaces to progress effectively
Teams will tend to create a component-per-team architecture driven by development speed, which may not be optimal for deployment, operation and maintenance
Deployment
A software system must be easily deployable, high deployment costs reduce its usefulness
Aim for a system that can be deployed with a single action
Deployment strategies are often neglected during initial development, leading to architectures that ease development but complicate deployment
For example, while microservices may simplify development with clear component boundaries, they can complicate deployment due to numerous services and connection configurations
Operation
Architecture has less dramatic effects on system operation compared to development, deployment and maintenance
Most operational issues can be addressed by adding hardware (ex. more servers, storage) without significantly altering the software architecture
Since hardware is relatively inexpensive, architectures that hinder operation are less costly than those affecting development, deployment and maintenance
A good software architecture communicates the operational needs of the system
The operation of the system should be apparent to developers, simplifying understanding and aiding in development and maintenance
Maintenance
Maintenance is the most expensive aspect of a software system due to ongoing feature additions and defect corrections
Key maintenance costs:
Spelunking: The effort of navigating through existing code to find the best place and strategy for changes, which can be resource-intensive
Risk: The potential for introducing new defects while making changes
A well-designed architecture reduces maintenance costs by:
Dividing the system into components
Using stable interfaces to isolate components
Clarifying pathways for future features which minimises the risk of unintended breakage
Keeping Options Open
Software has two types of value: behavior and structure, structure is more valuable as it enables flexibility
Keeping software "soft" involves maintaining as many options as possible for as long as possible, particularly regarding details that don’t impact the core behavior
Elements of software:
Policy: Represents business rules and procedures which is where the system's true value resides
Details: Include components like I/O devices, databases and frameworks which are necessary for communication but irrelevant to policy behaviour
The goal of an architect is to shape the system to emphasise policy and minimise dependencies on details, allowing decisions about those details to be delayed
Example:
High-level policies does not care what database is used
High-level policies does not care how it's being delivered over the web
Advantages of delaying decisions:
Delaying decisions allows for more experiments, better-informed choices and greater adaptability
If prior commitments exist (ex. to a specific database), a good architect structures the system to keep options open and decisions deferrable
Device Independences
In the 1960s, programmers often made the mistake of tightly coupling their code to specific IO devices like printers, card readers and punch cards
Problem: Device-specific code became problematic when hardware changed, all the software had to be rewritten to interact with the new hardware
Solution: Operating systems began providing a layer of abstraction, allowing programs to use generic services for IO operations. This made programs adaptable to different devices (ex. card readers, magnetic tapes) without rewriting code
This shift toward abstraction marked the birth of the Open-Closed Principle, which emphasises writing software that is open for extension but closed for modification
Junk Mail
Use case:
Problem:
Tasked with printing personalised junk mail, using huge rolls of pre-printed form letters. Names, addresses and other specific details were extracted from magnetic tapes through a program and were to be printed on the form letters
Initially, they used an IBM 360 and its single line printer for printing. However, the process was too slow and renting an IBM 360 was very expensive
Solution:
The program used IO abstractions provided by the operating system, meaning they could separate the logic for printing (policy) from the specific hardware (details)
This allowed them to switch the output from the line printer to magnetic tapes without changing the core program, reducing cost and increasing efficiency of printing
The ability to defer decisions about the output device provided flexibility and efficiency. The program didn't need to be concerned about the specific printer or device used, leading to a scalable and cost-effective solution
Physical Addressing
Use case:
Problem:
Storing different types of records (Agents, Employers and Members) on a 25MB disk
Each type of record had a specific size, so the disk was physically formatted in sectors tailored to the size of each record type
The software was hard-wired to understand the physical structure of the disk
Knows the exact number of cylinders, heads, sectors and each business rule was tightly coupled to this disk-specific addressing scheme
Upgrading to a different disk drive with more heads, cylinders or sectors was a difficult process
Had to rewrite code, migrate data and manually translate between the old and new disk structures
Solution:
A more experienced programmer suggested switching to a relative addressing scheme
Instead of hard-coding the disk's physical structure (cylinder, head and sector) into the business logic, the records could be treated as part of a single linear array of sectors
A small conversion routine would translate between the relative address (an integer) and the actual physical location on the disk (cylinder/head/sector) at runtime. This allowed the high-level code to remain agnostic about the specifics of the disk drive structure
Conclusion
Separate policy from details: Good design ensures that the core policies aren't directly tied to implementation details, like specific hardware or low-level mechanisms.
Decoupling enhances flexibility and maintainability
Delay decisions: The longer you can delay and defer decisions about details (such as hardware choices or specific implementations), the more options you keep open for adapting to future changes without needing significant rewrites