Grokking the System Design Review - everything your team should know

Explore the balance between upfront planning and evolutionary design, understanding the significance of architecture in software development, and learning how to conduct impactful system design reviews.

Grokking the System Design Review - everything your team should know

Software systems are often more complex than we initially perceive. It’s impossible to retain all the information and components that constitute a large distributed system in our brains forever. In addition to the sheer volume of data, our imperfect recall often leads us to forget critical details, past decisions, iterations, or simply how parts of a system function. Adding another layer of complexity, software systems are not — or should not be — static artifacts; they are ever-evolving and changing to meet business needs.

Software systems are so complex that we, as humans, continuously seek higher and higher levels of abstraction to try and make sense of the complexity of the bits of code we deal with on a day-to-day basis: this is why we organize lines of code into functions, functions into components, components into modules, modules into systems, systems into architectures.

Software architecture is a fundamental aspect of building a software system. It serves as a tool to guide the growth of the software in a way that keeps complexity in check and allows teams to communicate design choices and their implications, draw conclusions about the different behaviors of the system, and spot improvements or bottlenecks.

“Architecture is about the important stuff. Whatever that is.”Martin Fowler
“Architecture is the decisions that you wish you could get right early in a project.” — Ralph Johnson
“Architecture represents the significant decisions that shape a system where significant is measured by cost of change.”Grady Booch

Without the ability to consider the impact of our design decisions and establish a shared vision for the system (i.e., designing the software architecture), the complexity will grow into chaos.

However, there is considerable debate within the tech community regarding when teams should engage in software design and how much design is considered "enough." This article aims to explore these questions, guiding you through the nuances of system design in software development.

Big Design Up Front vs Agile Design

“After I had given an impassioned explanation of incremental design & refactoring, he paused, looked at me with those eyes, and said, “I suppose that’s all very well if you don’t know how to design software.” Mic. Drop.” - Conversation between Kent Beck and Niklaus Wirth

Websites and software used to be very expensive to build, so it was necessary to iron out as many kinks as possible before the costly engineering happened. Coupled with the prevailing approach of waterfall development, Big Design Up Front (BDUF) was the default approach: a website, app, or software design is completed and perfected up-front, before its implementation is started.

Agile aimed to move away from relying on predicting the future and months of planning and discussions, in favor of extensive prototyping, fast releases, and continuous deployment. The Agile Manifesto was an attempt to uncover a better way to develop software, knowing that the reality of the implementation can be far different from a theoretical blueprint of BDUF, business requirements can change over time, and new technologies could emerge, causing the market to demand different products.

Unfortunately, one of the tenants of the Agile Manifesto (”we have come to value: […] responding to change over following a plan”) was often interpreted as “don’t do software design if you want to be agile”.

In addition to this being a fundamental misunderstanding of the intention behind that sentence — in fact, the Manifesto creators say “while there is value in the items on the right [following a plan], we value the items on the left [responding to change] more” — the Agile principles themselves also imply that system design is important in “Continuous attention to technical excellence and good design enhances agility.

Those Agile teams who didn’t misunderstand the important of doing system design, often misunderstood how to do it due to these guidelines “Individuals and interactions over processes and tools” and “The most efficient and effective method of conveying information to and within a development team is face-to-face conversation.” They took this to mean no documentation and no diagrams.

While there can be value in having a conversation, we want to it to be a productive and effective conversation. The inherent complexity of software systems means that having abstraction visualizations, a system of records, and version control allows teams to focus on discussing the most significant changes or risks - instead of having to backtrack because of forgotten information, discordant visions, or unclear requirements.

We know that BDUF cannot work: we can’t ever predict the future. But we also know that we can’t have no design at all: we need to be able to see the bigger picture and effectively communicate about it.

The solution is to have some architecture design: have a shared understanding within the team of the vision for the software system and what are “the collection of constraints that we agree to adopt and maintain that will provide us some protection from making dumb mistakes” (Dave Farley, What Software Architecture Should Look Like). In other words, we want to think ahead before we build something, knowing that we're not going to get everything right and we are still going to have to evolve it as the business requirements change.

The Importance of “Some” Upfront System Design

“Big upfront design is dumb. No upfront design is dumber” - Dave Thomas

There have been many attempts to propose an alternative to BDUF, each suggesting a slightly different take on the necessary extent of upfront system design:

  • Rough Design Up Front (RDUF): “Significant parts of a solution can, and should, be designed up front. Not every project is so complex and uncertain that it must be evolved from scratch.” — John Harris
  • Sufficient Design: ”We need high design quality for stuff that is critical to our products and less design quality for stuff that isn't critical.” — Joshua Kerievsky
  • "Good Enough" Architecture: “There’s a fine line between diversity (that adds value) and chaos (that doesn’t).” — Stefan Tilkov

Ultimately, most experts agree on the importance of combining some degree of upfront design with an evolutionary design approach as you continue to build.

In other words, knowing that we won’t get everything right the first time in system design, we’ll want to adopt an iterative approach that enables incremental change over time and allows us to evolve the system’s functionality while protecting important architectural characteristics.

There are 3 main benefits to this approach:

(1) De-risked software development

It’s often the case that the most successful systems end up with the worst architectures: when a solution works, we minimize the importance of investing in the system architecture and validating our early assumptions. However, systems are constantly evolving, and an unmanaged evolution without any architectural constraints, will lead to complete chaos and make it harder to course correct when an issue arises.

  • Early Identification of Significant Decisions: Upfront design helps in identifying the unknown unknowns and making significant decisions about technology, structure, and strategies early in the process. Early commitment to these elements allows teams to understand and deliberate the trade-offs associated with each decision, effectively de-risking future development.
  • Mitigating the Cost of Change: Changes in software development are inevitable but making these changes later in the process can be costlier. By choosing the right architecture style and establishing a well-structured code base with high modularity and clear functional boundaries from the start, the impact of changes can be contained, minimizing the ripple effects through the code base and thus reducing the overall risk.

(2) Increased team collaboration

Instead of working in silos and managing separate backlogs, teams are encouraged to understand and evolve the product together, ensuring that all the various functionalities are well-integrated and collaboratively refined.

  • Shared Vision and Understanding: Upfront design brings teams together to discuss and agree upon the system's vision and have a common language and set of concepts to use as a reference point for future discussions, reviews, and evaluations.
    This is especially beneficial for complex or large-scale systems where different teams might work on different parts of the system: everyone from product management to frontend engineers, to backend engineers, to DevOps, to quality assurance, etc. is on the same page. This ultimately reduces miscommunications or divergent assumptions about system functionality and design.
  • Early and frequent Integration: It balances the iterative nature of agile with the holistic approach of system design.
    On one hand, teams are encouraged to integrate their work early and often, verifying that the various components of the system work well together. This prevents a situation where developers create sophisticated parts in isolation, but when they integrate them into the final solution, they face severe compatibility and functionality issues and project delays.
    On the other hand, a well-considered upfront design helps maintain a broader vision and ensures that the incremental progress doesn't come at the cost of the system's overall integrity or user experience.
    This approach strikes the balance between a narrow focus on individual sprints (with the potential for a fragmented and inconsistent design) and deep, holistic design thinking that focuses on overall polish and cohesion.

(3) Cost-conscious software

as speed of execution becomes more important we kind of lost this art of architecting for cost.” - Werner Vogels, Amazon’s CTO

When designing a system there are functional (product features and user requirements) and non-functional (security, compliance, accessibility, performance, availability, scalability, and maintainability) requirements.

Cost should be considered a non-functional requirement, along with its close counterpart, sustainability. Especially because cost-cutting is still a priority for CxOs and SaaS and IaaS cost consciousness will be at the top of engineering managers’ agendas in 2024.

Upfront design allows for informed decision-making that aligns with budgetary constraints and long-term maintenance costs. It enables:

  • Avoiding Over-Engineering: By outlining the necessary components and functionalities, teams prevent over-engineering or investing in unnecessary features, ensuring resources are allocated efficiently and development is aligned with actual business needs and constraints.
  • Performing Conscious Trade-offs: Every architectural decision involves trade-offs, particularly concerning cost. Understanding the implications of technology choices, structural strategies, and feature sets allows teams to design systems that meet performance and functionality needs without incurring unnecessary expenses.
  • System Visibility and Observability: An unobserved system is a breeding ground for unknown costs. Upfront and continuous architecture visualization is crucial for effectively understanding and managing costs effectively. By maintaining visibility into the system's architecture, teams can identify areas of inefficiency, potential bottlenecks, and opportunities for cost optimization. This continuous awareness enables teams to measure, analyze, and improve the system's cost-effectiveness throughout its lifecycle (exactly what we want to achieve at Multiplayer! 😉)

How Much System Design Is Enough?

We know that “some” upfront design is necessary and we’re talking in terms of hours and days (not weeks/months/years). However, definitively answering this question is impossible: there is no fixed formula, one-size-fits-all, generically-good software architecture.

Every software architecture design must consider specific business attributes and identify a unique sweet spot for each system, ensuring the architecture is not overly sophisticated but just sophisticated enough.

As a general guideline, your team has completed “enough” upfront design when they:

  1. Understand the significant architectural decisions (requirements, quality attributes, constraints). The purpose of upfront design is to provide initial direction; the team should understand what they are building and have a reasonable plan for construction, especially when addressing the highest risks.
  2. Able to communicate your technical vision to other stakeholders. This involves creating clear, understandable diagrams and documents that reflect the proposed architecture. These artifacts should convey the structure, components, behaviors, and interactions within the system, making it accessible not only to the development team but also to business stakeholders, product managers, and potentially customers.
  3. Confident that your client will endorse your solution. The early design work, while rough, must instill confidence in clients or stakeholders that the proposed solution is viable, aligns with their needs, and is worth investing in. This means it should address critical pain points, meet necessary functional and non-functional requirements, and be feasible within given constraints, such as time, budget, and existing infrastructure.

While the initial upfront design may be rough and high level, its goal is to establish a strong foundation and direction, minimizing the need for major redesigns later on as the project progresses and more detailed design work naturally emerges.

Beyond “Upfront” System Design

Upfront design is typically associated with Greenfield projects (brand new solutions). However, the harsh truth is, unless you’re part of a startup or bootstrapping your own project, starting an architecture from scratch is rare nowadays. The majority of projects developers work on in their professional career will be Brownfield projects: existing solutions that require new features, maintenance, or refactoring.

Indeed, 'upfront system design' is often linked to the concept of 'new system architecture', but that's a very narrow interpretation of this important practice. System design should occur upfront for any development work, including when releasing new features, adding or changing APIs, refactoring existing solutions, or migrating to new technologies.

Perhaps labelling it as 'continuous system design' would be a more accurate depiction, as it continues throughout the lifecycle of the product. Effectively, any time you make a change that evolves the application, you should also consider the impact and possible optimization paths for the system architecture. This approach allows the team to continually assess and fine-tune the architecture, data flow, and integration points to identify any potential bottlenecks or areas for improvement based on shifting business requirements.

Implementing continuous system design also ensures that the reasons behind key decisions are not forgotten. It maintains alignment on critical tech debts and mitigates any lack of context or ambiguity that may slow down the pace of development.

How to Implement System Design Reviews

System Design Reviews are an essential step in software development for teams to assess the technical feasibility, functionality, and performance of a system and identify dependencies and risks to make informed decisions.

These reviews pivot on a comprehensive examination of the overall system architecture, ensuring a well-structured design capable of handling intended workloads.

However, the tools and methods used during these reviews can significantly impact their effectiveness.

Traditional methods like whiteboard brainstorming, while interactive, may fall short in capturing the dynamic nature of evolving systems. On the other hand, documentation can become cumbersome, scattered across multiple platforms and leading to disjointed understanding.

When conducting system design reviews, it's crucial to leverage tools and practices that facilitate clarity, coherence, and comprehensive understanding.

Avoid wasting precious time on deciphering static diagrams or collating information from disparate sources. Instead, focus on fostering meaningful discussions that drive towards solving complex problems and making informed design decisions.

By choosing the right approach and tools for your system design reviews, you can ensure that your team not only understands the architecture at hand but also collaborates effectively to refine and evolve it, steering your project towards success

Architecture visualizations play a pivotal role in planning, deploying, and managing your software system. Don’t use general purpose whiteboarding tools or scattered architecture documentation, use a purpose built, collaborative tool for teams that want better solutions for system design and architecture documentation. Check out Multiplayer and sign up for free!