28,99 €
Software Architecture with Kotlin explores the various styles of software architecture with a focus on using the Kotlin programming language. The author draws on their 20+ years of industry experience in developing large-scale enterprise distributed systems to help you grasp the principles, practices, and patterns that shape the architectural landscape of modern software systems.
The book establishes a strong foundation in software architecture, explaining key concepts such as architectural qualities and principles, before teaching you how architectural decisions impact the quality of a system, such as scalability, reliability, and extendability. The chapters address modern architecture topics such as microservices, serverless, and event-driven architectures, providing insights into the challenges and trade-offs involved in adopting these architectural styles. You’ll also discover practical tools that’ll help you make informed decisions and mitigate risks. All architectural patterns in this book are demonstrated using Kotlin.
By the end of this book, you’ll have gained practical expertise by using real-world examples, along with a solid understanding of Kotlin, to become a more proficient and impactful software architect.
Das E-Book können Sie in Legimi-Apps oder einer beliebigen App lesen, die das folgende Format unterstützen:
Seitenzahl: 659
Veröffentlichungsjahr: 2024
Software Architecture with Kotlin
Combine various architectural styles to create sustainable and scalable software solutions
Jason (Tsz Shun) Chow
Copyright © 2024 Packt Publishing
All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews.
Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for any damages caused or alleged to have been caused directly or indirectly by this book.
Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information.
Group Product Manager: Kunal Sawant
Publishing Product Manager: Teny Thomas
Book Project Manager: Manisha Singh
Content Development Editor(CDE): Rosal Colaco
Technical Editor: Kavyashree K S
Copy Editor: Safis Editing
Proofreader: Rosal Colaco
Indexer: Rekha Nair
Production Designer: Joshua Misquitta
First published: December 2024
Production reference: 1031224
Published by Packt Publishing Ltd.
Grosvenor House
11 St Paul’s Square
Birmingham
B3 1RB, UK
ISBN 978-1-83546-186-0
www.packtpub.com
To Marta and Mieszko Yin-jo.
Jason (Tsz Shun) Chow has been working in the software industry since 2001. He is a Senior Vice President at CAIS, an alternative investment platform, with a focus on the complex domain modeling of private equity and hedge funds, combined with unified design and the use of artificial intelligence. Prior to this, Tsz Shun was a Principal Engineer at 11:FS, a digital fintech consultancy focused on building digital strategies and lovable products aimed at changing the fabric of financial services. In the early 2010s, Tsz Shun worked at IG Index on several latency-sensitive financial exchange services and authored the public APIs in the FIX protocol.
I want to thank the people who have been close to me and supported me, especially my wife and my parents.
Jose Coll is an experienced Financial Services IT professional with over 20 years of experience in systems implementation, specializing in electronic trading, risk management, and valuation systems for OTC and exchange-traded derivatives. He has worked with JVM languages, middleware messaging, and distributed, parallelizable compute technologies. For the past 7 years, Jose has focused on Blockchain and Distributed Ledger Technology (DLT) as a lead engineer, building the Corda platform using Kotlin and exploring various aspects of Blockchain, Crypto, and Web3.
Alexei Timofti, with over 18 years of experience spanning startups and global enterprises, specializes in building complex and scalable systems. Currently a Principal Engineer at N26, he has spent the past five years leveraging Kotlin to craft a banking experience people love to use. Starting his programming journey with Java, he discovered Kotlin at N26 and quickly fell in love with its concise and clean syntax. Since then, Kotlin has become his preferred language for backend development.
Alexei is passionate about designing clean architecture, data engineering, and championing test-driven development to deliver robust and maintainable solutions. In his free time, he enjoys traveling, reading, and spending time with his closest ones.
Welcome to Software Architecture with Kotlin, a comprehensive guide designed to equip you with the knowledge and skills needed to build robust software systems using Kotlin. As the demand for efficient and scalable applications continues to grow, understanding architectural principles becomes indispensable for engineers and architects.
In this book, we will break down a selection of software architecture styles into basic components by the first principle, so these components can be rearranged and combined to solve real-world problems. Each chapter focuses on specific areas, introducing key concepts, best practices, and real-world examples illustrating how to apply these principles in Kotlin.
Whether you are a seasoned engineer looking to deepen your understanding of software architecture or a newcomer eager to learn, this book provides practical insights that can be immediately applied. You will find hands-on exercises, code snippets, and case studies that will help you grasp complex ideas and implement them in your projects.
As you embark on this journey, I encourage you to experiment with the concepts presented here. Software architecture is more than patterns and styles – it is about creativity, problem-solving, flexibility, and adapting solutions to meet the unique challenges of your applications. Happy reading!
This book is for software engineers who want to enhance their architectural knowledge and mindset to solve daily engineering problems. Prior experience in engineering will be useful but not essential.
If you have just started learning how to write Kotlin code, are expanding your technical experience from Android development to backend, or are transitioning from writing Java code, you will find this book useful.
If you are a software architect interested in discussing and exploring unique architectural ideas, this is the book for you.
Chapter 1, The Essence of Software Architecture, revisits the importance of software architecture and the role of software architects in an organization. It covers how the structure of an organization affects architectural decisions. Then it discusses choosing a framework and the factors to consider in the process. Several industry-standard documents and diagrams are introduced, which will be used for illustrations in subsequent chapters.
Chapter 2, Principles of Software Architecture, explores multiple ways to visualize and quantify software architectures, extracting quality attributes for measurement and analysis. It then delves into three key concepts: the separation of concerns, cohesiveness, and coupling. Popular architecture principles such as SOLID, the Law of Demeter, YAGNI, and future-proofing are also covered. These principles lay the foundation for further exploration of architecture styles in the following chapters.
Chapter 3, Polymorphism and Alternatives, uses a real-life problem and solves it using multiple styles in Kotlin code. It starts with a polymorphic solution, then explores two solutions involving Kotlin sealed classes. Next, a solution using delegation is presented, followed by a functional approach. Finally, all approaches are compared based on system quality attributes.
Chapter 4, Peer-to-Peer and Client-Server Architecture, focuses on network communication in distributed systems. It includes a step-by-step guide to implementing a client-server solution with an API-first approach using OpenAPI specifications and the Http4K framework in Kotlin. The chapter then implements a peer-to-peer solution to the same problem and compares the two approaches, discussing which is suitable for different situations.
Chapter 5, Exploring MVC, MVP, and MVVM, shifts focus to frontend applications. Using a sample Android application, we apply MVC, MVP, and MVVM to observe the evolution of the implementation with different architecture styles. The three patterns are compared along with other commonly used styles.
Chapter 6, Microservices, Serverless, and Microfrontends, moves the focus to the backend. This chapter shows how monolithic applications and service-oriented architectures evolve into microservices and nanoservices. It explains how serverless architectures impact modern software systems through cloud provider services. Finally, the frontend counterpart of microservices, the micro-frontend, is discussed.
Chapter 7, Modular and Layered Architectures, starts with three layered architectures—Clean architecture, Hexagonal architecture, and Functional Core Imperative Shell—that have similarities and differences. They are demonstrated and compared using Kotlin code for the same real-life problem. Later, the chapter explores the Connect pattern, providing a modular approach to integrate with remote systems.
Chapter 8, Domain-Driven Design (DDD), takes a deep dive into DDD design activities. It starts with basic concepts and terms, then looks at the bigger picture of the domain with Strategic Design. A bounded context is selected for Tactical Design. The chapter also walks through three popular domain modeling activities with a real-life example.
Chapter 9, Event Sourcing and CQRS, extends the DDD practices from the previous chapter into two powerful architecture patterns. It first illustrates the use of Event Sourcing with a real-life example, then explains how CQRS can be applied. Finally, it combines Event Sourcing and CQRS as a solution to the same problem to unlock the potential of both architecture styles.
Chapter 10, Idempotency, Replication, and Recovery Models, discusses three related architectural concepts. It starts with idempotency in distributed systems, providing practical examples of how to implement it. Next, several replication models are explored and compared using the CAP Theorem. The chapter concludes with system recovery, using RAFT leader election as a case study.
Chapter 11, Auditing and Monitoring Models, demonstrates a sample audit trail structure in Kotlin that can be used by multiple services but centrally recorded. It also discusses various monitoring data formats and approaches for collecting data for monitoring purposes. The chapter covers structural and contextual logging with Kotlin code, as well as automated alerts, incident management, and metrics.
Chapter 12, Performance and Scalability, focuses on measuring performance using defined metrics. It showcases performance testing through basic approaches and micro-benchmarking. The chapter guides you through performance testing workflows while discussing strategies for performance improvement using Kotlin code. Additionally, a voting system is used to illustrate the process of enhancing performance and scaling the system.
Chapter 13, Testing, explores the role of Quality Assurance. It examines various testing methods within the Testing Pyramid and highlights best practices for each type. The chapter includes a step-by-step journey through a Test-Driven Development exercise with the Kotest framework.
Chapter 14, Security, focuses on safeguarding software systems and their data from malicious attacks. It starts with securing network communication using Transport Layer Security (TLS). Then, the chapter covers Multi-Factor Authentication (MFA) for user identity verification. It also addresses common methods of authorization and data entitlement, techniques for hiding and anonymizing sensitive data, and various network security approaches. The chapter concludes with a discussion of DevSecOps and a Threat Modeling exercise.
Chapter 15, Beyond Architecture, covers various engineering topics beyond software architecture. First, it explores several Kotlin language features that help engineers achieve better code quality and software architecture. Next, it discusses the transition from Java to Kotlin with the help of IDE features. The chapter compares two CI approaches: feature-based and trunk-based development. It then covers release strategies, briefly touches on Developer Experience, and concludes with a look at current trends in software architecture.
It is essential to approach the material with an open mind and a willingness to experiment. Start by revisiting the foundational concepts presented in the first two chapters, as they serve as the building blocks for more advanced topics. Engage with the hands-on examples and coding exercise, which are designed to materialize your understanding for practical application. Reflect on how each architecture style can meet the needs of your projects. In addition, consider exploring external resources to further enrich your learning experience.
Software covered in the book
Operating system requirements
IntelliJ IDEA (Community or Ultimate version)
Windows, macOS, or Linux
Android Studio
Windows, macOS, Linux, or ChromeOS
OpenJDK 17+
Windows, macOS, or Linux
Git CLI tool
Windows, macOS, or Linux
You will need to configure IntelliJ and Android Studio to use the installed JDK and Git CLI tool.
If you are using the digital version of this book, we advise you to type the code yourself or access the code from the book’s GitHub repository (a link is available in the next section). Doing so will help you avoid any potential errors related to the copying and pasting of code.
After reading the book, I would recommend you start a new project to try to solve the real-life example problem of household exchanging services in a village, or to solve a problem that you know a lot about, e.g. a problem that you face in your day-to-day life or work. Meanwhile, apply the architecture styles, analyze them with your knowledge of the problem, and explore the frameworks that were mentioned in the book. Solving a well-understood problem helps us focus on addressing non-functional concerns and hands-on coding.
You can download the example code files for this book from GitHub at https://github.com/PacktPublishing/Software-Architecture-with-Kotlin. If there’s an update to the code, it will be updated in the GitHub repository.
We also have other code bundles from our rich catalog of books and videos available at https://github.com/PacktPublishing/. Check them out!
There are a number of text conventions used throughout this book.
Code in text: Indicates code words in text, database table names, folder names, filenames, file extensions, pathnames, dummy URLs, user input, and Twitter handles. Here is an example: “The Person class directly accesses the city property inside the Address class.”
A block of code is set as follows:
class Person(val name: String, val address: Address) { fun getAddressCity(): String { return address.city } } class Address(val city: String)Bold: Indicates a new term, an important word, or words that you see onscreen. For instance, words in menus or dialog boxes appear in bold. Here is an example: “The Business package uses the Persistence package to perform the actual relational database operations for service contracts and households.”
Tips or important notes
Appear like this.
Feedback from our readers is always welcome.
General feedback: If you have questions about any aspect of this book, email us at [email protected] and mention the book title in the subject of your message.
Errata: Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you have found a mistake in this book, we would be grateful if you would report this to us. Please visit www.packtpub.com/support/errata and fill in the form.
Piracy: If you come across any illegal copies of our works in any form on the internet, we would be grateful if you would provide us with the location address or website name. Please contact us at [email protected] with a link to the material.
If you are interested in becoming an author: If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, please visit authors.packtpub.com.
Once you’ve read Software Architecture with Kotlin, we’d love to hear your thoughts! Please click here to go straight to the Amazon review page for this book and share your feedback.
Your review is important to us and the tech community and will help us make sure we’re delivering excellent quality content.
Thanks for purchasing this book!
Do you like to read on the go but are unable to carry your print books everywhere?
Is your eBook purchase not compatible with the device of your choice?
Don’t worry, now with every Packt book you get a DRM-free PDF version of that book at no cost.
Read anywhere, any place, on any device. Search, copy, and paste code from your favorite technical books directly into your application.
The perks don’t stop there, you can get exclusive access to discounts, newsletters, and great free content in your inbox daily
Follow these simple steps to get the benefits:
Scan the QR code or visit the link belowhttps://packt.link/free-ebook/978-1-83546-186-0
Submit your proof of purchaseThat’s it! We’ll send your free PDF and other benefits to your email directlySoftware architecture is the blueprint of a software system. It may not contain a single line of code but it describes how different structures work together so that systematic behaviors emerge from these structures, and thus the system serves its intended functions.
This is a book for those who wish to boost their architectural knowledge and mindset to solve daily engineering problems. In this chapter, we will discuss the essential values of software architecture and its position in an organization. We are going to cover the following topics:
The importance of software architectureThe role of an architectConway’s lawChoosing a frameworkDocumentation and diagramsWhy should we bother with software architecture? In theory, a good engineer can simply jump into coding. Given time and effort, a software system can be produced to start functioning. This is a typical example of jumping to the result without extracting the value from the process.
A software system is a living entity that needs to adapt to the changes in the environment. Let us use a real-life example to illustrate this concept.
In a village community, every household offers help to each other. One household’s members have certain skills lacking in another household. A member in household A is good at plumbing but not good at making clothes, while a member in household B is a tailor but the household needs pipes fixing.
So, household A offers to fix the pipes of household B in exchange for household B making clothes for a newborn baby in household A.
Each household uses bookkeeping software to keep records of the exchange of services in each household’s file. Each copy of the software in each household does not communicate with the other.
It works well for a while until some households have a dispute over what was agreed in their exchange of services. Both households claimed their records were correct in the software; however, the records in each copy of the software are slightly different. Since each copy of software does not communicate with the other, the dispute cannot be easily resolved.
One of the possible enhancements of the bookkeeping software would be to keep the records in a central data store so that households can view and agree on the details of the exchange of services before carrying out their services.
However, the bookkeeping software was written without architecture. All we have are lines and lines of codes, scattered in multiple files, and with some duplicated logic in multiple places. The code itself may be well-written and organized, but the original engineer has left the village, and the new engineer does not understand the rationale behind the code.
Software architecture is fundamentally a way of communication. Firstly, it defines what problems it solves in an abstract manner that stakeholders from non-engineering backgrounds can understand and reason about the software system.
Stakeholders use specific terms in describing the problem. Sometimes, different stakeholders use different terms that mean the same thing, or they might use the same term but mean different things. Engineers will also need to align with the terms and usage in the engineering structures. Software architecture acts as a common language and understanding so that all stakeholders and engineers can communicate with well-defined terms.
Usually, stakeholders make use of software architecture to integrate with their operation workflows. They may have other systems to interact with, or they need teams of people to work in various parts of the system. Software architecture becomes a visualization of the automated part of the workflow.
Secondly, software architecture provides an abstract view of how different structures work together and focuses on certain concerns at a time. A new engineer joining the team usually has a lot to learn to understand how the current system works. Source code is the ultimate source of truth; however, it could be laborious and time-consuming to read it all. Source code is usually cluttered with language syntax and layers of function invocations. Building up an understanding of the system from the code bottom-up is certainly possible, but it would take a long time.
Learning is much more effective with architectural documents that guide new members directly to the areas they care about. It is less overwhelming than source code, and it avoids engineers treating the bugs in code as the correct behaviors. New engineers can learn one aspect of the system at a time, with the aid of architectural documents.
System quality attributes, also known as system non-functional attributes, are the characteristics of a software system that define its overall behaviors, and operational and performance aspects. They are non-functional in that they are agnostic to the functional or business problems the system solves.
System quality attributes, such as availability, scalability, security, testability, extendability, and maintainability, are difficult to measure with only code. Software architecture provides at least one view to manifest each of these attributes so we can tune the system accordingly.
In the given example, the software could be lacking redundancy in the sense that each copy of the software stores the data in its own local storage and does not communicate with any other. If a copy has stopped working, the household would lose all data. Also, because each copy does not communicate with the other, there is no reliable way to guarantee that two households who exchanged services have the same records in their own software copies.
By having software architecture to describe the system attributes, engineers will be able to identify the issue and design a change to improve the given attributes. Moreover, it enables us to measure and monitor how these attributes change over time and correlate them with software changes. We are even able to project and predict these attributes when we plan a change to the current software architecture.
Usually, problems change and evolve over time. In the example, separated records of the exchange of services in each copy of the software were sufficient, as there was not a dispute. Software architecture provides a foundation for changes and enhancements. In many cases, different stakeholders have different priorities in their minds. Software architecture facilitates the discussion of how the system could evolve and at what cost, so the enhancement can be prioritized in order.
Also, with system attributes being described in software architecture, we can identify and mitigate risks since we understand which part of the architecture is being changed.
Software architecture documents a series of concerns raised and decisions made. In the example of the bookkeeping software, since the original engineer has left the village, no one really knows the thinking process and why certain design choices were made at the time. It becomes very risky to enhance the system as no one knows the impact of changing one line of code. The idea of a central data store was planned and we are just one step behind it, or it was never designed to share data. We simply do not know.
This leaves us unable to safely improve the software, or even just to fix a bug. We might end up making the same mistake. We might misunderstand the original intent of the software and even create a bug. It becomes difficult to continue using the software if the problem evolves like the given example.
Software architecture acts as a set of records of decisions made to solve the problem. It explains the rationale of what drove the decisions and what factors were considered to make the choice. It also records any alternatives considered and why they were not eventually chosen.
Software architecture also identifies any constraint the system is bound to. It is important to include constraints because any new technological advancement may eliminate such constraints, such as new frameworks, and thus create new opportunities for improvement.
All this information provides solid ground if, one day, we decide to start a new system from scratch to solve the problem. We will not need to start from zero. We can start from what we have learned and the journey behind it. We can reuse a lot of the concepts from previous architecture if the context is applicable. We can significantly improve the next system with fewer constraints imposed on the previous system.
It may seem obvious that a software architect (the architect) is someone who creates software architecture. However, software architecture is the result of multi-dimensional thought processes that involve a lot of people. There is no single architect who would produce architecture alone and require no input from others.
It is important to point out that, although a software architect can be a job title in some organizations, the role of a software architect is not restricted to only someone who has the title.
Software architects align and translate the language used by engineers and non-technical people (the stakeholders). They facilitate communication using documentation and diagrams to illustrate key topics in the software system for discussion. There are variations in how the interface works between engineers and different stakeholders with the facilitation of software architects. We are going to explore these variations now.
Software architects translate product requirements into technical designs. Engineers can do the same, but software architects apply a broader view in the sense of how certain implementations may impact system quality attributes. Software architects do not dictate the choice of implementation; however, they define non-functional requirements that predict system quality attributes. The non-functional requirements provide directions and constraints on the implementation.
In the example given previously, if a software architect were involved in the technical design process, they could have required the records of the exchange of service between neighbors to be replicated in both software copies and thus could have avoided the dispute of inconsistent records.
Software architects also take part in translating technical constraints, bugs, and implementations into information that product managers can digest and engage in. Software architects provide an abstract view of the code implementation to facilitate communication with product managers.
Imagine there is a new framework that facilitates two software copies to synchronize records of service exchange between neighbors, which permanently solves the dispute problem. Software architects can document this new approach and abstract the interaction to provide a foundation to discuss with product managers how this improves user experience.
There is often tension when it comes to engineers developing features and delivery managers managing the timeline for when those features can be released. It is common for engineers to not deliver the full features in time. Software architects can facilitate the discussion of how the features may be delivered in phases and still be operational. In each phase, software architects determine the impact on system quality attributes and how users can operate in the meantime.
This is just an example of how software architects are involved when full features will not be available in time.
Software systems, particularly in regulated industries, must address compliance concerns. The range is wide, and it may include the processing of personal data, auditing of persisted data, or complying with regulatory procedures.
Software architects are not only involved in designing an architecture that complies with regulations but also in illustrating how it was implemented. Regulatory bodies will examine technical documents, including architecture diagrams, as part of their due diligence process.
People who specialize in the fields of information security or cybersecurity work with software architects in multiple areas.
They provide security requirements in line with security policies, procedures, and guidelines. The requirements might include authentication, access control, and even the choice of encryption algorithms.
Software architects work with security analysts to perform threat modeling and risk assessment. They analyze the system architecture, identify vulnerabilities and risks, and discover potential attacks. The likeliness and impact of threats drive architectural choices.
Software architects may also work with penetration testers or ethical hackers to discover security holes and potential fixes.
Security architects collaborate with software architects to identify and choose the approach to address identified risks and meet security requirements.
Stakeholders usually come from multiple departments of the organization, and they are likely to have different requirements and priorities for how the system is required to work. Software architects can navigate these tangled requirements and ensure that the system can fulfill these requirements in an agreed priority order.
Software architects also play the part of extracting common terms from multiple domain experts and stakeholders so the terms can be used in the architecture documents in a clear and unambiguous manner.
While some software architects might be keen on having the most state-of-the-art technology and the latest and the fastest, realistically, they are more balanced with the budget the organization can afford.
Financial constraints on technology choice do not necessarily result in bad architecture; on the contrary, they encourage software architects to find more cost-effective ways to solve problems, and they could lead to a leaner and simpler architecture. If two architectures can address an identical set of concerns, the simpler and cheaper one is always better.
The decision of whether to buy or build is often affected by multiple factors, and the technical factor is only one of them. Although software architects may not have the power to decide which way to go, they provide technical and operational analysis so the organization can make an informed choice.
When the organization cannot afford the most technically sound system or service, software architects are there to bring out compromise, trade-offs, and impact analysis for the “second-class solution.” It may seem not ideal initially, but software architects can design the system in a way that leaves room for enhancement and expansion in the future.
Legacy systems are outdated software systems that are still in use by the organization. They are legacy because their technology has very little room for improvement, and it is likely at least a few years backward.
There are systems that became legacy due to external factors such as discontinued technology support and severe limitations. And there are no feasible or cost-effective ways to evolve.
Legacy systems can also be the result of the lack of technical vision and roadmaps, in which software architects are heavily involved. Some small start-ups may not have someone taking the role of software architect, or there is no one championing software architecture continuously. These can all be reasons for systems to become legacy.
However, software architects can still jump in at any point to modernize the current architecture. They start by understanding what the current system does and what the organization really needs. Then, they decompose the system into autonomous parts, modernize them individually, and recombine them in a separate way so the whole technical ecosystem can be up to date again.
Usually, a technical vision includes inspiration in achieving a software architecture that manifests certain system quality attributes, such as highly available and scalable systems. While a technical roadmap includes small steps to achieve short- to medium-term goals, and some more dramatic changes to long-term goals, it requires meticulous planning and thought toward how the system evolves. Also, the technical roadmap must interact with the external technological evolution to pivot and adapt to a better alternative.
Cross-cutting concerns are typically the concerns that require multiple software components to work together to derive the desired outcome.
One example can be standardizing logging messages so they can facilitate cross-service log searches.
Engineers are often divided into teams and each team looks after a certain area of business. They do not necessarily have the bandwidth to ensure that services in other teams conform to the same convention to achieve cross-cutting outcomes.
Software architects engage these cross-cutting concerns in a holistic manner. They consult, engage, and discuss with multiple teams to form a consensus or convention so cross-cutting concerns can be addressed.
Software architects also drive common infrastructures, frameworks, and tooling to address these cross-cutting concerns. These concerns are closely related to the system quality attributes.
Let us say there are multiple services that need to communicate with each other, and REST endpoints are chosen to be the way of communication. However, without establishing standards among teams, the system quickly falls into a collection of inconsistent APIs. The URI resource hierarchy can be inconsistent, as can the error response payload. All these impact the maintainability and reusability of the system.
Software architects can be involved in understanding each team’s requirements and their concerns about using REST endpoints. Then, a guideline of REST endpoints can be created so that there is a pattern that engineers align with. A typical example would be to define a general payload structure for error responses to contain information in addition to the HTTP response status:
{ "resource": "/users/32039/address/0", "shortMessage": "first line of address must be present", "longMessage": "A valid address must contain the first line", "details": { "addressLine1": null, "city": "London", "postCode": "EC12 10ED", } }This sample payload represents an error of an address input; it contains general fields such as resource, shortMessage, and longMessage that every service can conform to, while also having a details section to be customized by each service.
By having this standard, we can achieve overall observability of these errors and persist them in a universal format for audit purposes. Engineers can reuse this structure to reduce the time needed to develop a new REST endpoint. Engineers will also find it easier to maintain a REST endpoint even if it was developed by other engineers.
In a sense, standardizing the REST error payload has addressed the cross-cutting concerns of observability, auditability, maintainability, and reusability in the whole technical ecosystem.
Conway’s law is an observation that the system design of an organization mirrors the organizational structure. A computer programmer called Melvin Conway introduced this idea in 1967, and his original wording is as follows:
“Any organization that designs a system (defined broadly) will produce a design whose structure is a copy of the organization’s communication structure.”
In the context of software systems, software architecture mirrors the organization structure. The classic example can be illustrated in this diagram:
Figure 1.1 – A company organized by skill set
The company has a backend engineering (BE) team, a frontend engineering (BE) team, and a database engineering (DE) team. This organization groups people by their skill set. Everyone in a team is responsible for all business functions. This structure is likely to produce a monolithic system, which usually manifests in a single source code repository or one single logical process.
Figure 1.2 – A company organized by business functions
The organization in Figure 1.2 groups people by their business functions. So, everyone in a team is responsible for a designated business function, but each member may not have the same skill set. This structure is likely to produce a modular system, which contains multiple logical processes that interact with each other. Usually, there are dedicated source code repositories for each team.
Systems scale better when the team size is small because the number of communication channels required for people to talk to each other is n (n – 1) / 2 so it is exponentially scaled up. Jeff Bezos from Amazon proposed his two-pizza rule:
“If you can’t feed your team with two large pizzas in a meeting, you’re in trouble.”
So, if teams cannot be too big to scale the organization as well as the system, then it usually ends up with many teams. This resonates a lot with the architectural concepts we will cover very soon.
On the other hand, despite best efforts in modernizing the architecture of a legacy system, if the organization structure refuses to align with it, it is likely that the new architecture will eventually fall back to its old habitual structure.
This is something that should be solved by engineering management and upward. It is beyond what software architects can solve. However, it is worth understanding this phenomenon so the issue can be escalated as soon as possible.
Some big organizations found it extremely difficult to change their structures. And they even created start-up companies to run with modern organization structures alongside modern software architecture.
A software framework (a framework), or a software development framework, is a standardized set of tools that aim to solve certain problems by consistent approaches.
A software system typically needs quite a few frameworks, so that they can focus on the business functions instead of lower-level concerns such as logging, JSON transformation, and configuration management. These frameworks provide a proven way to achieve the target software architecture. Choosing a framework is a part of architectural decision-making.
It is rare that organizations build every framework themselves these days. The major reason is that most of the frameworks are open-sourced and supported by the community. It would take a lot of justifications for an organization to decide to develop its own framework while there are similar competing frameworks that can be used for free.
Some technology companies develop their own frameworks when there are no existing ones that suit their needs. Some companies develop their own frameworks with the intent to compete with the other frameworks, and to potentially monetize from consulting business or to cross-sell their other products. It would take a lot of research effort and talent to achieve that.
The other option would be to choose a framework that already exists in the market, commercial or open-sourced.
New frameworks are released every month with the intent to solve the age-old problems of existing frameworks. Usually, there are one or two popular frameworks on the market, and the new frameworks advertise that they solved the old ones with an approach that everyone has always wanted.
Of course, there are true paradigm-shifting new frameworks that made engineers more productive and really have moved the industry forward with an innovative approach. For example, Ruby on Rails has transformed the repetitive and boilerplate code configuration of web development into inference and conventions, hence vastly reducing the number of lines of code.
But there are also a lot of cases where the new frameworks started with innovative approaches that did not go too well. And here comes the new framework paradox.
If a new framework aims to replace a framework that has been around for many years, the new framework will need to cover a lot of areas and keep the “new approach” in each area. This is a huge undertaking for the contributors.
For example, the Spring frameworks were created in 2002 to simplify dependencies of code by using Dependency Injection (DI) and Inversion of Control (IOC). But now, the frameworks have evolved to cover an extensive range of features, such as web, messaging, security, persistence, and so on. A next-generation framework to replace Spring frameworks would have to cover over 20 years of development, with a very comprehensive coverage of technical areas.
The most significant risk is that the new framework may have solved one of the longest-standing problems of a framework but it falls short of the areas that are fundamental and essential. It traps the engineers who adopt the new framework and makes them face the dilemma of whether to fix the new framework or return to the old one.
Another risk is that the community may not agree on what the “new approach” should be, and therefore, multiple new frameworks are created to solve the same age-old problem of the old framework. Engineers who want to try a new framework face the choice overload problem. And sometimes, it becomes a choice paralysis as there is no single definitively better choice to choose from.
Let us say your team has chosen a framework and everyone is quite happy with it. However, for whatever reason, the major contributor has decided to not work on this project anymore. Then, your team is at risk of the framework not being kept up to date with the fixes and planned enhancements. Not to mention that most open-sourced frameworks are contributed by normal engineers who spend their personal time for free on this.
However, in a real situation, the team would still need to choose some frameworks to move forward. An example situation would be a framework for logging messages for a Java application. Do we use the Java Logging framework that comes with the standard Java Development Kit (JDK), Apache Log4J, or Logback? How could we make the most sensible choice? Unfortunately, there are no golden rules that guarantee the best choice, but there are several aspects that the team should consider before making the decision.
Community is the most crucial factor in your consideration. People are the reasons the framework is created, used, and maintained. Without people, the framework will not continue. There are at least three areas of the community for the framework to look for:
Firstly, the bigger the community, the more likely the framework will have someone to continuously support and enhance the framework. A framework should be like a living being, powered by the people in the community. Also, reasons for having a large community for a framework are likely that the framework is universally applicable and of acceptable quality for general usage.Secondly, we need to look at how well the framework is supported by the community. It could be as simple as getting help from another user on how to use the framework. It could also be the quality and quantity of technical blogs written by the members of the community to share their tips on how to apply the framework to problems. It could be measured by suggestions the community made for new features and enhancements.Thirdly, we need to see how the members of the community communicate with each other. Do they have a Slack channel, a Discord server, an email distribution list, or any instant messaging platform? How responsive are the members of the community when people post their questions out there? Are the people helpful and positive in receiving feedback?Every commit to the source code repository made up the framework the way it is now. It is worth checking some statistics to understand how actively the framework is being maintained.
When was the last commit? Was it recently updated? How many commits have been made so far? Also, we can check the number of commits in the last month, the last 6 months, or the last year. Moreover, we can look at the variety of contributors. A good sign is that the commits are done by a variety of contributors, not only the usual ones. It indicates a diverse and healthy growth from contributors putting their efforts into the framework.
How many forks and branches are there? Bigger numbers usually indicate healthy growth that either some members of the community are working on a change or there could be a variant of the framework soon. It is likely that there are useful features already in the code base that people are willing to spend their effort on.
The number of tags indicates historical releases and may give a hint about the evolution and growth of the framework. However, be careful of versions under 1.0 (e.g., 0.67), or simply just build numbers. The contributors in this case may not want to commit to the current shape of the framework, and there may be breaking changes in the future.
Versions under 1.0 also could mean contributors may not have confirmed their commitment to keep the framework running for long yet. Extra caution must be taken if you intend to put a 0.x library dependency in your production system. It is going to be difficult if the library discontinues or introduces breaking changes.
We should also look at the source code and get an impression of the code’s quality and test cases. We should glance at the test coverage to understand how deep and broad the code was tested. This would help us predict the reliability and stability of the framework.
We should also consider whether the framework uses mature tooling to manage itself. It may include an issue tracking system that members of the community can submit bugs and track how long it takes for a bug to go from reported to fixed.
The framework may also use an established Continuous Integration (CI) system. This is also a good sign of a healthy, long-running, and mature framework since there is a need to automate builds to handle the number of commits, control the quality, and release the framework.
Documentation is a key factor to consider since this is where engineers learn how to use the framework. The documentation does not necessarily need to be polished or automatically generated. It is the quality of the content that matters. And diagrams would be nice if they help engineers understand the concepts.
Many frameworks were designed to work with other frameworks, and some of them have innate dependencies on other frameworks. This is common and not a bad sign; however, caution must be taken on the impact.
Adopting a framework that uses or works with another framework implies we are also indirectly adopting the other framework. Is the other framework compatible with the engineering approach the team has taken? Do we allow engineers in the team to use the transitive dependencies directly in the code?
Even if we are OK with the other framework, we still need to ensure that the versions are compatible. For example, framework A may have used the Apache Commons IO library, version 2.14.0, and our project currently uses 1.4. Importing framework A to our project would bring version 2.14.0 as a dependency to the project. Luckily, build frameworks such as Gradle and Maven provide a graceful way to explicitly specify a version and exclude a particular version from the transitive dependency. In this example, we will upgrade our dependency on Apache Commons IO to 2.14.0 from 1.4 to use framework A.
Engineers might want to build their own framework instead of choosing an existing one. Under certain conditions, this could be beneficial.
If the software has unique requirements that cannot be met by existing frameworks, then it would justify building a bespoke framework. It could be a very specific domain, or it could have very strict non-functional requirements. For instance, engineers for High-Frequency Trading (HFT) software might write their own framework to meet ultra-low-latency requirements.
Building a bespoke proprietary framework might also be justified if the organization treats it as a competitive advantage in the market with cutting-edge technology.
It may also be the start of a new open-sourced framework in the community if no such framework has existed before. In this case, it may be beneficial to gather engineering talents among the communities and collaborate.
Despite all our best efforts, we might still have chosen the wrong framework. The framework may not have delivered the intended behaviors. The contributors may have given up on the project. The framework may have taken a novel approach that no longer suits our needs.
The adoption of the wrong framework becomes technical debt. Unfortunately, we need to source a replacement framework and plan the refactoring works to remove this dependency.
The technique of refactoring is beyond the scope of this book, though. And it is not always possible to avoid choosing the wrong framework. All we can do is exercise our due diligence in the process of decision. If appropriate, we can also create interfaces so that only minimal classes in the code base have direct reference to the framework, while the framework usage to the rest of the code base is transparent.
Software architecture as a blueprint of the system is captured in documentation and diagrams. Some of them could be captured in configuration files and templates, but when software architects need to present the system or communicate with stakeholders, documentation and diagrams are still the most used formats. Some of these diagrams will be used in upcoming chapters.
Software systems, at a high level, can be seen as automated business processes that can be visualized in diagrams. Business Process Model and Notation (BPMN) standardizes graphical notations and provides a common language for modeling business processes. It is commonly used among engineers and stakeholders for communication and documentation purposes.
Taking the example of two households coming to a mutual agreement on the contract of services they exchange (the service contract), the business process could be modeled as follows:
Figure 1.3 – Example of a BPMN diagram
Household A and Household B have their own swimlanes to illustrate the process on each side. Household A submits a draft of the service contract and Household B receives it. Household B reviews the draft and submits its decision. If Household B rejects the service contract, then both processes at Household A and Household B end. Otherwise, Household B waits for Household A’s response; meanwhile, Household A records the service contract, and the process ends. Finally, Household B receives the service contract from Household A and records the service contract, and the process ends.
BPMN has a rich collection of notations to describe business processes. They can be categorized into four groups.
Activities can be tasks and sub-processes that happen in the business process. Events are outcomes that have happened. Gateways are the points where a decision is made or the process splits into branches.
Sequences illustrate the flow of control and the messages communicated among flow objects. Associations describe the relationship among objects, such as inputs, outputs, or dependencies.
Swimlanes are the groupings of flow and connecting objects based on the roles and responsibilities of participants involved in the business process.
Artifacts are additional information to the diagrams, and they provide context such as the data objects involved or simply free-text annotations.
Software architecture can be seen as a journey from problem discovery to solution implementation. Along the journey, there are a lot of decisions made to move the system forward. An Architecture Decision Record (ADR) is a document that captures the decision made based on the context at that time and the consequences coming with it.
There are many ADR templates available on the internet, which conceptually cover the following sections.
This is typically just a single word to describe the current state of the ADR in the process. Here is an example of the ADR process:
Figure 1.4 – An example of the ADR process
The basic possible states are Proposed, Accepted, and Rejected. In this example, there are other states, such as Under Review and Changes Required. It varies from one organization to another.
This section should introduce the background where the discussions started. A good introduction would bring the needs of the change to the current situation e.g., pain points of the current operations, organization restructuring, business expansion, etc.).
It also introduces the terms that are used throughout the discussion so they can be easily referred to without ambiguity. A bit of the current organizational structure and technical infrastructure would also be helpful.
If applicable, this section can mention the current system quality attributes and why we want to change them. For instance, if our system can only handle 100 concurrent logins and the company wants to support 10,000 in the new technical design, then scalability is the system attribute this ADR proposes to change.
It should also mention the desired outcomes. This sets up a target state we want our change to achieve. The motivation here should refer to the problems mentioned previously and elaborate on how the outcome could improve business results.
This section describes the proposed change in detail. It should focus on how the change would produce the desired outcome described in the previous section. It may also mention the concerns raised and how the decision was driven by the discussion.
In some cases, alternative changes are mentioned. If they are mentioned, there should be a comparison between the proposed change and the alternatives. One way to compare is to list the pros and cons of each option. Another way could be to compare each option against a list of factors and conclude why an option is proposed.
This section describes the impact of choosing the proposed change. Does it change the way the team operates? Which system attribute would it change and how? Does it optimize one aspect of the system but sacrifice another aspect? Which part of the system may become obsolete?
Request for Comments (RFC) is a series of documents in which standards, protocols, procedures, and guidelines are proposed, discussed, agreed, and defined.