Modifiability, Security, Testability in Software Architecture
Modifiability
Here, the stimulus is request for change, and the goal of modifiability tactic is to ensure that changes can be made, tested and deployed within time and budget.
It is important to keep in mind that:
1. We only need modifiability of a component when it's reused or will be replaced by another component in the future..
A component that might be reused later will need high modifiability. If you know that there's a module that will, in the future, work with better, different technology component (say new database), you need modifiability.
2. We need to know the domain well to consider where modifiability might be needed (possible area that might be improved later.
Stimulus is the 'request for change', but what change?
Functionality, quality attributes, hardware can change. Example for hardware: storage might have to increase while software is running.A developer, system admin, end user might want to request for change. The change might be requested at implementation, compilation, installation, execution time.
Tactics for modifiability
- reduce size of module
- Why? bigger the module, harder to understand.
- How? split modules
- reduce coupling (probability that change to one module will propagate to another module)
- Why? high coupling is higher dependency. so harder to change
- How? increase encapsulation, use intermediaries (interfaces), restrict dependencies, refactor, abstract common services. Minimise interactions to what's necessary
- increase cohesion
- Why? single responsibility in one module, so single reason for change
- How? semantic coherence: whatever exist in the same module should be relevant
- Defer binding time: defer binding values late as possible
- Why? Example: if we want to make change to an app being used by millions of users, we can't request all the users to update the app. Instead, we design architecture so that developer can make change to the server side, and make server notify client of the change, and client automatically changes its configuration. (one of the reason client server architecture is useful - single point of change for changing service)
- How? Defer binding at different times. no hardcoding, use DIP (dependency inversion) to allow for DI (dependency injection)
- Compile time: parameterised compiler
- deployment time: configuration file
- initialisation time: resource file
- run time: dynamic binding (DI)
Modifiability example
Security
Security usually consists of CIA: confidentiality, integrity, availability
CIA | Tactic 1 | Tactic 2 |
confidentiality: only authorized users can access sensitive information |
Authentication: verifying who the user is Authorisation: verifying what user has access to |
apply encryption so that only those with decryption key can read data. apply physical security: physically secure sensitive information (eg password) |
integrity: data protected from unauthorized changes |
Backup: can rollback to valid state | Checksum: if any change in data, we can identify it |
availability: data should be available timely and uninterrupted note: availability depends on our specification, as said in the previous post |
redundancy (most common): have spare servers | physical protection: against natrual disaster and muggler |
React to attacks
- if there’s an access, we revoke that access. limit users/what user is doing
- computer locked so user can’t log in or use the computer
- notify relevant parties, so they can take necessary actions
Recover from attacks (if there are damages)
- Restore state (rollback, similarly in availability)
- audit trail: recored each transaction and the identity associated to it (so record who’s made the damage)
Testability
we want to detect faults in unit before it gets to production
Tactic methods | Description | Why |
specialized interfaces | getter and setter, that are isolated fromother parts of the system | use them to set state of the system: increase controllability when testing |
record and playback | record what caused the fault, and play it back to test system | we can reproduce faults through playback, and attempt to fix it |
localizing state storage | have a state configured for the system in the local storage | reproducible state: when I want to run test in certain state, we set it up using local storage |
sandboxing (important!) | when testing, have an isolated 'developement' environment so that we can run functionalities of the system that we want to test, but not actually make any impacts in the domain | Testing a missile: you don’t want to launch the missile. without actually launching the missile we test it Testing transaction: you just want to mimic the behaviour to be tested, without affecting the rest of the system |
abstract data source | design system so that data can come from any source: eg test data substituted easily (dummy data, instead of actual data). | can test easily with local data, without network connection to database etc |
executable assertions | make sure that conditions that you assume before certain behaviour and after behaviour are what you expected. | allows for assertions of pre/post conditions of the system |
Limit structural complexity | loose coupling, high cohesion. | If structurally complex, harder to test. eg polymorphism make things complex to test the module |
limit nondeterminism | ensure behaviours are predictable | we want to test reproducible behaviours and fix reproducible bugs. Example: in multithreading there can be contention, which causes unexpected behaviours. record and playback helps to relieve the issues from inevitable nondeterminism |