

Study with the several resources on Docsity
Earn points by helping other students or get them with a premium plan
Prepare for your exams
Study with the several resources on Docsity
Earn points to download
Earn points by helping other students or get them with a premium plan
Community
Ask the community for help and clear up your study doubts
Discover the best universities in your country according to Docsity users
Free resources
Download our free guides on studying techniques, anxiety management strategies, and thesis advice from Docsity tutors
This cheat sheet describes the clean, simple, flexible, evolvable and agile Software architecture
Typology: Cheat Sheet
1 / 2
This page cannot be seen from the preview
Don't miss anything!
Software architecture is the high level structure of a software system, the discipline of creating such structures, and the documentation of these structures. [1] It is the set of structures needed to reason about the software system, and comprises the software elements, the relations between them, and the properties of both elements and relations. [2] In today’s software development world, requirements change, environments change, team members change, technologies change, and so should the architecture of our systems. The architecture defines the parts of a system that are hard and costly to change. Therefore we are in need of a clean, simple, flexible, evolvable, and agile architecture to be able to keep up with all the changes surrounding us.
An architecture that allows to replace details and is easy to verify.
Entities : Entities encapsulate enterprise-wide business rules. An entity can be an object with methods, or it can be a set of data structures and functions. Use cases : Use cases orchestrate the flow of data to and from the entities, and direct those entities to use their enterprise-wide business rules to achieve the goals of the use cases. Interface adapters : Adapters that convert data from the format most convenient for the use cases and entities, to the format most convenient for some external agency such as a database or the Web. Frameworks and drivers : Glue code to connect UI, databases, devices etc. to the inner circles. Program Flow : Starts on the outside and ends on the outside, but can go through several layers (user clicks a button, use case loads some entities from DB, entities decide something that is presented on the UI)
The concentric circles represent different areas of software. In general, the further in you go, the higher level the software becomes. The outer circles are mechanisms. The inner circles are policies. Source code dependencies can only point inwards. Nothing in an inner circle can know anything at all about something in an outer circle. Use dependency inversion to build up the system (classes in an outer circle implement interfaces of an inner circle or listen to events from inner circles).
The architecture does not depend on the existence of some library of feature-laden software. This allows you to use such frameworks as tools, rather than having to cram your system into their technical constraints.
The business rules and use cases can be tested without UI, database, Web server, or any other external element.
The UI, database, or any other external element can easily change without any impact on use cases and business rules.
An architecture that is easy to understand. Simplicity is, however, subjective.
One problem has one solution. Similar problems are solved similarly.
Simple solutions make use of only a few different concepts and technologies.
The less interactions the simpler the design. A reasonable amount of components with only efferent coupling and most of the others with preferably only afferent coupling.
Small systems/components are easier to grasp than big ones. Build large systems out of small parts.
Build your system by connecting independent modules with a clearly defined interface (e.g. with adapters).
An architecture that supports change.
Divide your system into distinct features with as little overlap in functionality as possible so that they can be combined freely.
When the structure and interactions inside the software match the user’s mental model, changes in the real world can more easily be applied in software.
Separating ideas from specific implementations provides the flexibility to change the implementation. But beware of over abstraction
.
Fat interfaces between components lead to strong coupling. Design the interfaces to be as slim as possible. But beware of ambiguous interfaces
.
Inheritance increases coupling between parent and child, thereby limiting reuse.
The dependency graph of the elements of the architecture has no cycles, thus allowing locally bounded changes.
An architecture that is easy to adapt step by step to keep up with changes.
The architecture of the current system should match the current needs (functional and non-functional) – not some future ones. This results in simpler, easier to understand solutions. Otherwise, the risk of waste is very high.
The current architecture should be extendable and adaptable so that future needs can be addressed. When evaluating different alternatives, choose one that is open for change.
When components don’t care about which architecture they run in, the architecture can be changed without having to rewrite the components.
When the software has outlived its architecture, throw the architecture away and start over. This mindset can be used to build a first version with a very simple architecture, then start over for the next.
When a new version of a concept is introduced, then the old one is refactored out step by step. There can be at most two versions of a concept in an application (and it should be temporary).
An architecture that supports agile software development by enabling the principles of the Agile Manifesto [6].
The architecture allows quick changes through flexibility and evolvability.
The architecture can be verified (fulfils all quality aspects) at any time (e.g. every Sprint).
The architecture supports continuous and rapid deployment so that stakeholders can give feedback continuously.
The system is always working (probably with limited functionality) so that it is potentially shippable any time/at end of Sprint. Use assumptions, simplifications, simulators, shortcuts, hard-coding to build a walking skeleton.
Use a top-down approach to find the architecture.
What belongs to your system and what does not? Which external services will you use?
Split the whole into parts by applying separation of concerns and the single- responsibility principle.
Which data flows through which call, message or event from one part to another? What are the properties of the channels (sync/async, reliability, …)
Repeat the above-mentioned three steps for each part as if it were your system. A part is a bounded context, subsystem or component.
Decide only things you have enough knowledge about. Otherwise find a way to defer the decision and build up more knowledge. A good architecture allows you to defer most decisions.
Use an abstraction to hide details so that you don’t have to decide about the details, but can use a simulation/fake at first to build up more knowledge.
Simplify the problem so that a decision can be made and work can progress. Use this to break free from a blocking state, but be aware of the risks a wrong decision could have.
Refuse to decide and wait until more knowledge about the problem and its potential solutions is built up.
Build the (part of a) system in a way that doesn’t require any decision, by making some other (part of the) system responsible that can be implemented later. E.g. instead of deciding how to persist data, make the code calling your code responsible for passing all needed data to your code. This allows you to build your whole business logic and decide about persistence when implementing the host that runs the business logic.
The needed quality attributes (functionality, reliability, usability, efficiency, maintainability, portability, …) are the primary drivers for architectural decisions.
The whole team understands and supports architecture and can make design decisions according to the architecture.
How easy an envisioned architecture can be implemented is a quality attribute.
Most costs of a software system accrue during operations, not implementation.
Every technology, library, and design decision has its risks.
Things the architecture would allow us to do (but without investing any additional effort because we may never need it).
Availability of new (better) technologies, resulting in a need for architecture change.
Designing an architecture comprises making trade-offs between conflicting goals. Trade-offs must reflect the priorities of quality attributes set by the stakeholders. Trade-offs should be documented and communicated to all stakeholders.
Introduction of design decisions into a system’s actual architecture that are not included in, encompassed by, or implied by the planned architecture.
Introduction of design decisions into a system’s actual architecture that violate its planned architecture.
Different parts of the system claim ownership of the same data or their interpretation resulting in inconsistencies and difficult synchronisation.
E.g. shared code to remove duplication hinders independent advancements, a service that needs other services to be up and running, an initialise
method that has to be called prior to any other method on the class (better use constructor injection or a factory).
A design decision that prevents further adaptability without a major refactoring or rewrite.
Direction of Dependencies
Use Cases
Interface Adapters
Frameworks & Drivers
Entities UI
External Interfaces
Web
Program Flow
time
knowledge
learn decide (^) enrich
Concrete implementations are easier to understand than generalised concepts.
Configurability leads to if/else constructs or polymorphism inside the code, resulting in more complicated code.
Don’t design for reuse before the code has never actually been used. This leads to overgeneralisation, inapt interfaces and increased complexity.
First, make it work, then optimise. Premature optimisation leads to more complex solutions or to local instead of global optimisations.
Use quality scenarios to guide your architectural decisions because most of the times, quality attributes have more impact than functional requirements.
Big systems are more complicated to comprehend than a combination of small systems. But beware of complexity hidden in the communication between the systems.
Teams themselves are empowered to define, develop, and deliver software, and they are held accountable for the results.
Simplicity leads to comprehensibility, changeability, low defect introduction.
Get real feedback from running code, then decide.
Testing is an integral part of building software, not an afterthought.
The whole team participates in architecture decisions.
Every team member has time to innovate (spikes, hackathons, pet project).
Don’t think in technologies, think in concepts. Then choose technologies matching the concepts and adapt concepts to technological limitations.
Break your architecture work into steps. Use assumptions and simplifications in early steps. Always make sure that there is a path from the current architecture to the envisioned architecture.
You always have some data. But that is no reason to start your design with the database. Business logic and workflows are more important.
Design everything so that it has to know nothing about its environment.
Break risks and grow knowledge fast, then decide.
Architecture patterns are good examples of solutions to specific problems. Use them to find solutions for your problems and do not apply them to your problems.
Form of data (document-based, relational, graph, key-value), backup, transactions, size of data, throughput, replication, availability, concurrency.
Static (e.g. resources) vs. dynamic, switchable during implementation/installation/start-up/runtime.
Asynchronous/synchronous, un-/reliable, latency, throughput, availability of connection, method calls/events/messages.
Run on multiple threads/processes/machines, availability, consistency, redundancy.
Authentication, authorisation, threats, encryption (of communication and data). See [9]
Operations, granularity, access to journal, tampering, regulatory.
Access to data (production/dedicated database/data warehouse), delivery mechanism (synchronous/asynchronous), formats (Web, PDF, …).
Available time frame for migration/import, data quality, default values for missing values, value merging/splitting.
Release as one, per service or per component (e.g. plug-in). Automatic or manual release.
One product vs. a product family, technical/marketing version, manually or automatically generated, releases/service packs/hot fixes, SemVer. [10]
APIs, data (input/output/persisted), environment (e.g. old OS).
Service time (actually performing the work) + wait time + transmission time
Data growth rate, access to archived data, split relations in relational data.
Beware of the fallacies of distributed computing: the network is reliable, latency is zero, bandwidth is infinite, the network is secure, topology doesn’t change, there is one administrator, transport cost is zero, the network is homogeneous.
Versioning, immutability and stability of contracts and schemas.
Who is the consumer? What do they need? How do you deliver the documentation to them? How do you know when they are ready for it? How do you produce it? What input do you need to produce it?
Manual: someone writes the documentation, high risk of being out-of-date, very flexible Automatic [12]: generated from code, can be regenerated anytime and is therefore never out of date, finding right level of abstraction is hard. Works good for state machines, bootstrapping mechanics, and structural breakdown.
Only document what you did, not what you want to do.
The whole team participates in producing the documentation.
Causes : applying a design solution in an inappropriate context, mixing design fragments that have undesirable emergent behaviours.
When there are layers on layers on layers on layers ... in your application. Not providing abstraction, lots of boilerplate code.
Too abstract to be understandable. Concrete designs are easier to understand.
Everything is configurable because no decisions were made how the software should behave.
A simple problem with a complex (however technically interesting) solution.
The architecture wants to anticipate a lot of future possible changes. This adds complexity and most likely also waste.
Lots of new cool technology is introduced just for the sake of it.
The architecture exists only on paper (UML diagrams) with no connection to the reality.
A component doing the job that should be delegated to a connector: communication (transfer of data), coordination (transfer of control), conversion (bridge different data formats, types, protocols), and facilitation (load-balancing, monitoring, fault tolerance).
A single concern is scattered across multiple components and at least one component addresses multiple orthogonal concerns.
Ambiguous interfaces are interfaces that offer only a single general entry point into a component (e.g. pass an object , or general purpose events over an event bus). They are not explorable.
Two connectors of different types are used to link a pair of components. E.g. event (asynchronous) and service call (synchronous). Event : loosely coupled availability, replicability. Method call : easy to understand. Both : neither.
[1] P. Clements, F. Bachmann, L. Bass, D. Garlan, J. Ivers, R. Little, P. Merson, R. Nord and J. Stafford, Documenting Software Architectures: Views and Beyond, 2nd ed., Boston: Addison-Wesley,
[2] Wikipedia, “Software architecture,” [Online]. Available: http://en.wikipedia.org/wiki/Software_architecture. [Accessed 2015].
[3] R. C. Martin, “The Clean Architecture,” [Online]. Available: http://blog.8thlight.com/uncle-bob/2012/08/13/the-clean- architecture.html. [Accessed 2015].
[4] M. Fowler, “Sacrificial Architecture,” [Online]. Available: http://martinfowler.com/bliki/SacrificialArchitecture.html.
[5] [Online]. Available: https://lostechies.com/jimmybogard/2015/01/15/combating-the- lava-layer-anti-pattern-with-rolling-refactoring/.
[6] “Manifesto for Agile Software Development,” [Online]. Available: http://agilemanifesto.org/principles.html. [Accessed 2015].
[7] K. Henney. [Online]. Available: http://www.artima.com/weblogs/viewpost.jsp?thread=351149.
[8] D. Leffingwell, “Principles of Agile Architecture,” [Online]. Available: http://scalingsoftwareagilityblog.com/wp- content/uploads/2008/08/principles_agile_architecture.pdf. [Accessed 2015].
[9] [Online]. Available: https://www.owasp.org.
[10] [Online]. Available: http://semver.org/.
[11] [Online]. Available: http://thinkrelevance.com/blog/2013/10/07/begin-with-the-end-in- mind.
[12] [Online]. Available: http://www.planetgeek.ch/2014/06/17/effective-teams-know-your- code/.
[13] J. Garcia, D. Popescu, G. Edwards and N. Medvidovic, “Toward a Catalogue of Architectural Bad Smells,” [Online]. Available: http://softarch.usc.edu/~josh/pubs/qosa_2009.pdf. [Accessed 2015].
Legend:
Urs Enzler www.bbv.ch June 2015 V1.
This work by Urs Enzler is licensed under a Creative Commons Attribution 4.0 International License.