In this post I would like to share some Architectural Principles that every Software Architect need to understand or be aware of. The architecture practise area is so broad, open and convoluted within Agile software development. Furthermore, the ephemeral nature of most modern applications (should I say Functions, micro-services, Lambda’s) as they iterate and become something else over years, delivering varied business value makes it even more interesting to apply these principles to systems and applications as they evolve in this era of Artificial Intelligence (ML, DL, API AIs) and Serverless.
ARCH 1: Single Responsibility principle
Each component or module should only be responsible for a specific feature or functionality, or aggregation of cohesive functionality. Remember single responsibility – thanks to Serverless and Function as a Service!
ARCH 2: Separation of Concerns
Divide application into distinct features with little or no overlap in functionality. The important factor is to minimise interaction points to achieve high cohesion and low coupling. Note: Separating functionality at wrong boundaries can result in high coupling and complexity even though the contained functionality within a feature does not significantly overlap.
ARCH 3: Principle of Least Knowledge (aka Law of Demeter)
A component or object should not know about internal details of other components or objects.
ARCH 4: Don’t repeat yourself (DRY)
You should only need to specify intent in one place. For example, in terms of application design, specific functionality should be implemented in only one component; the functionality should not be duplicated in any other component.
ARCH 5: YAGNI – You ain’t gonna need it (Minimize upfront design)
Only design what is necessary – sounds easy but isn’t simple in software architecture within Agile frameworks. In some cases, you may require upfront comprehensive design and testing if the cost of development or a failure in the design is very high. Especially for agile development, you can avoid big design upfront (BDUF). If your application requirements are unclear, or if there is a possibility of the design evolving over time, avoid making a large design effort prematurely. KISS and fail fast is my other favourite principles.
ARCH 6: Define clear contract for components
Components, modules, and functions should define a contract or interface specification that describes their usage and behaviour clearly. The contract should describe how other components can access the functionality of the component, module, or function; and the behaviour of that functionality in terms of pre-conditions, post-conditions, side effects, exceptions, performance characteristics, and other factors.
ARCH 7: Do not overload the functionality of a component
Overloaded components often have many functions and properties providing business functionality mixed with crosscutting functionality such as logging and exception handling. The result is a design that is very difficult to maintain and often error prone. Applying the single responsibility and separation of concerns principles will help you to avoid this.
ARCH 8: Do not mix different types of components in the same logical layer
Start by identifying different areas of concern, and then group components associated with each area of concern into logical layers. For example, the UI layer should not contain business processing components, instead should contain components used to handle user input and process user requests.
ARCH 9: a) Understand how layers communicate with each other b) Understand how components will communicate with each other
Allowing every layer in an application to communicate with or have dependencies on all other layers will result in a solution that is more challenging to understand and manage. Make explicit decisions about the dependencies between layers and the data flow between them. How components communicate warrants deep understanding of the deployment scenarios your application must support. You must determine if all components will run within the same process, same container, or if communication across physical or process boundaries must be supported – perhaps by implementing message-based interfaces.
ARCH 10: Keep the data format consistent within a layer or component.
Mixing data formats will make the application more difficult to implement, extend, and maintain. Every time you need to convert data from one format to another, you are required to implement translation code to perform the operation and incur a processing overhead.
ARCH 11: Keep crosscutting code abstracted from the application business logic
Crosscutting code refers to code related to security, communications, or operational management such as logging and instrumentation. Mixing the code that implements these functions with the business logic can lead to a design that is difficult to extend and maintain. Consider using frameworks and techniques (aspect oriented programming) that can help to manage crosscutting concerns.
You must consider when architecting your applications:
- Instrumentation and Logging
- Exception Management
- Protocol and Communication
ARCH 12: Determine Deployment Strategy
Your application may be deployed in a variety of environments, each with its own specific set of constraints such as physical separation of components across different servers, a limitation on networking protocols, firewall and router configurations, and more. Think about diagnosable, recoverable, and others!
ARCH 13: Determine Appropriate Technologies
Your choice of technologies will also be governed by organization policies, infrastructure limitations, resource skills, and so on. You must compare the capabilities of the technologies you choose against your application requirements, taking into account all of these factors before making decisions.
ARCH 14: Determine Quality Attributes (Fitness functions)
Every application design must consider security and performance, but not every design needs to consider interoperability or scalability. Understand your requirements and deployment scenarios first so that you know which quality attributes are important for your design. Keep in mind that quality attributes may conflict; for example, security often requires a tradeoff against performance or usability or debuggability as security checks (Auth & Authn) or constraints could cost performance.
Questions one should ask when considering quality attributes include:
- What are the key quality attributes required for your application? Identify them as part of the design process.
- What are the key requirements for addressing these attributes? Are they actually quantifiable?
- What are the acceptance criteria that will indicate that you have met the requirements?
ARCH 15: Designing for Operation, Keep DevOps or SecDevOps in your thoughts
Design for Operability & Supportability. Architecting for production is key to delivery of artefacts. Also total cost of ownership (TCO) is something that is critical to business – from a P&L perspective. I see operations (you build it, you own it) as an on-going nightmares given the scope of whats involved. At a very high level things such as security, pen test, data breaches, compliance, performance test, vendor support, SaaS HA / SLA, cloud & hybrid public cloud, etc is critical to business and operations team. At an application level failure handling, retry, 3rd party integrations, monitoring and alerting, etc evolves over time and operations tend to become complex.
These principles cover broad areas of software architecture addressing various aspects such as business architecture, software architecture & design, software development, hosting, operations & support, customer data & experience, software evolutions, re-factoring, re-architecting, and most importantly business value delivered from an architecture practise should be on the rise. I believe these principles stay relevant and valid even today and next foreseeable future.