Processes of Decomposition
Posted on November 22nd, 2021
Part of Notes on Software Design
The design of a system outlines what the system is and is not for, describes the overall architecture following which the system will be developed, and details its static structure and dynamic behaviour.
At this point, we have some amount of user profiles/personas, scenarios, storyboards, wireframes, and (non-)functional requirements. We can now startdecomposing the system concept into a theoretical representation.
Documenting the design should be done various levels of complexity/abstraction. Without simplification, the design process would be trivial since it would match the implementation identically.
Functional design concentrates on the functionality of the system, whereas operational Design concentrates on the operational aspects; e.g., hardware set-up, what functionality runs where, network design. With the movement in modern software development towards the cloud, this is becoming less relevant.
Good design addresses and includes extensibility, modularity, reusability, maintainability, usability, and clarity.
Balance for all involved
When designing an application, a requirement will have many possible design solutions; the users, developers, and maintainers must all be considered, as an imbalance will lead to detriment for everyone.
For example, over-simplified applications will be easier to create but the end-user will not be happy with the system
By contrast, complex systems will be harder to develop and maintain; this costs time and money which ultimately affects the end-user.
Using patterns
Most 'new' solutions are simply an adaptation of existing applications, so the wheel need not be reinvented. More importantly, use design patterns which are relevant to the solution. e.g., N-tier or lter for a .NET API written with C#.
Design patterns also accommodate for changes and maintenance, as new designers will have a fundamental understanding of the system before tacking domain knowledge, implementation, etc.
Representation
Designs should be represented with models because they're a proven and well-accepted method.
They should demonstrate the functionality of the system at different levels of complexity/abstraction. This makes the design more accessible, as the system can be understood at the level desired by an individual. Inherently, it is important to ensure a representation is not oversimplified or important details may be masked.
Provided the developers of the system respect the design, a system's model will closely resemble the technical implementation of the system, so getting the models correct is key to smooth development.
Making Decisions
Although some aspects of the solution will have a clearly optimal design, most aspects of a project will require some level of decision-making.
As such, it is important to document any/all decisions. Not only does it simply provide evidence of the decisions made by the team, but producing a standardized document ensures all factors motivating the decision are valid.
Decision | [ID] - [Name] |
---|---|
Subject Area | Area of concern |
Topic | Topic of interest |
Summary | Summary of the decision made, ideally in the imperative tense |
Problem Statement | A short description of the problem, i.e., what is being decided |
Assumptions | What is believed to be true about the context of the problem |
Motivation | Why is the decision important? |
Alternatives | A list of alternatives and explanations |
Decision | The decision taken, possibly with references to related work |
Justification | Why the decision was made |
Implications | What impact the decision will have |
Justification | A list of requirements that are generated by this problem |
Implications | A list of related decisions |
System Boundary
(This is not well explained; most of this is my interpretation so it might be wrong 🤷♂️)
A system boundary determines where aspects of the system are sourced by decomposing the top-level functionality of the solution; basically:
- What is the purpose of the system?
- What the system does not do
- What are the external dependencies?
Think of it as an extension of scope; after defining what the system should do, the system boundary determines whether each aspect of the system is 'in scope' for creation or resourced from an existing solution.
System boundary also considers human sources for input/output, so these devices should be considered as well, e.g., PIN number entry pad.
Lastly, system environment refers to anything which affects/can be affected by the system, excluding the system itself.
Representation
A system context diagramis the typical way to represent the system boundary, represented as a simple, 'free form' sketch. The relationships between systems can be ambiguous and considers:
- Who/what is interacting with the target system?
- What are they doing and how?
Solution Structure
Following the system boundary, the solution structureidentifies the main conceptual elements (subsystems) of the system and their relationships; e.g., the core subsystem, connections, data stores, users, external systems, etc.
Think about:
- How the system will be accessed?
- What are the main conceptual elements?
- How the elements communicate with each other?
Representation
An architecture overview diagramis the typical way to represent solution structure. It is a (usually) static, informal diagram with supporting text.
The definition is pretty loose, so they can identify a wide range of subsystems. However, there are some typical organisational groupings: users help to identify the roles of people using the system; channels identify how the system can be used/accessed, e.g., Browser, Mobile, Integrated PoS terminal; application identifies the subsystems; and resources contain the systems used by the application. Additionally, splitting the groups further or color-coding can add more detail.
It does not need to be implementation-specific, i.e., 'Database' could be sufficient, but 'AWS Cloud Hosting' is also acceptable.
Refinement
Following the solution structure, the application's subsystems are iteratively (and recursively) designed in the refinement process:
- How can the conceptual elements be subdivided into subsystems?
- How do the subsystems look like?
- How do the subsystems interact with each other functionally?
Types of Subsystems
The definition of a subsystem is very flexible but it basically has to be sizeable. For example, look at the subsystems for a remote control:
- Case
- Power supply
- Circuit board
- Chips
- Fuses
- IR blaster
- Integrated circuits
- Capacitors
- Resistors
- Bridges
There are however some very common subsystem types.
ComponentA modular unit of functionality, usually identified the architecture definition.
Its state and operations are declared using one or more interfaces, so the definition should not be implementation specific.
They are functionally independent, meaning they do one thing.
Components are defined at a technical level, e.g., MessageQueue
, or application level, e.g.,
OrderProcessor
.
Class The usual.
Entity
A class with a conceptualisable existence, most often physical like a Person
, or not, like a
Report
(value objects say huh?).
ServiceA group of specifications, exposed through JSON, API, interfaces, etc.
They can be consumed, e.g., StockPriceService
, UserAuthoriser
, or provided using components/classes.
It's all about the process
Generally, this process considers the structure of each subsystem, and how they interact.
Start with identification of the subsystems, and assign responsibility, based on the requirements document. Define the relationships between subsystems and outline their interactions. Use existing design/architecture patterns to ensure the system interactions are clean and consistent.
Next comes specification, which tackles implementation detail by creating the interfaces for each subsystem. This includes their operations, i.e., parameters, return values; and their contract behaviour, such as pre- and post-conditions.
Guidelines
Cohesion The strength of dependency within the system. The goal is to create functionally independent but interdependent subsystems, meaning each subsystem has one responsibility and utilises other subsystems to delegate other responsibilities required to function.
❌ Low cohesion means a subsystem's functionalities have no meaningful relationship, only in time, i.e., a process may use them in the same block of code.
✔️ High cohesion means the opposite. For example, the functionalities can use each other (functional), use the output of one as an input for another (sequential), or compose a greater procedure to be used in order (procedural).
Coupling The strength of dependencies between subsystems. Good coupling allows dependencies to change their implementation without requiring changes to its dependants.
❌ Strong coupling means subsystems refer to the same global data 'area' and the internal logic of the subsystem cannot be isolated from the implementation of its subsystems.
✔️ Loose coupling the logic between subsystems to be isolated, and communication is handled with fundamental data types like domain entities or technical classes.
Isolation The degree to which the product depends on technologies. It also considers recognisability of its design and implementation.
❌ Low isolation means subsystems depend on technologies, such as a native app; and fail to use standard patterns, e.g., directing with a database directly.
✔️ High isolation means the opposite, e.g., a web app (any platform) which uses an ORM to omit database specifics from the application code.
Layering Partitioning the concerns/responsibilities of a large subsystem into general categories.
🤷♂️ Horizontal layering categorises by functional responsibility, e.g., presentation, business, and data layer. This is referred to N-tier architecture.
🤷♂️ Vertical layering categorises by business/domain responsibility, e.g., a
query, handler, and view model toCreateProduct
. One example is onion
architecture.
Types of Models
Static models A static, logical view of the application including state and relationships (e.g., entity relationship, class, component, service model).
Dynamic models (or behavioural models) demonstrate the interactions between the subsystems (e.g., use-case diagram, sequence diagrams).
End-user interactions are modelled with use-case diagrams.
Technical interactions within a subsystem show the complex behaviours of the implementation like method and service calls, modelled with flowcharts or activity diagrams.
Modelling between systems is captured with interactions models: when ordered by time, a sequence diagram; when ordered by organization, collaboration or communication diagrams.