Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 76

Architectural Alternatives & Core Principles

Recommended resources

Introduction

Welcome to the first lesson of our module on "The Craft of Principal-Level Design." In our previous modules, we've dissected existing systems, diagnosed failures, and pinpointed performance bottlenecks. Now, we shift our perspective from being reactive to proactive—from analyzing systems that are to designing systems that will be. This transition is fundamental to the role of a Principal Engineer, whose primary responsibility is to navigate complex, ambiguous problems and lay the architectural foundations for robust, scalable, and evolvable systems.

Today's lesson addresses the learning outcome: Generate multiple, distinct architectural alternatives for a complex system design problem, articulating the core principles behind each. We will move beyond the search for a single "right" answer and instead focus on the strategic practice of exploring the design space. You will learn to identify the fundamental principles that act as levers in a design, allowing you to generate distinct architectural options and articulate the trade-offs inherent in each. This skill is the starting point for making defensible, high-impact technical decisions.


Why Not Just One "Right" Architecture?

A common anti-pattern in system design is to latch onto the first plausible solution that comes to mind. This often leads to a design that is a local optimum at best, and a deeply flawed foundation at worst. Principal-level design begins with the recognition that any sufficiently complex problem has multiple valid solutions, each optimizing for a different set of goals.

Generating multiple alternatives forces you to:

  1. Deepen Your Understanding: You cannot create distinct options without first deconstructing the problem's core tensions—the inherent trade-offs between goals like latency, cost, consistency, and operational complexity.
  2. Make Trade-offs Explicit: Each alternative becomes a concrete embodiment of a particular set of priorities. This moves the discussion from abstract "what-ifs" to a tangible comparison of different futures for the system.
  3. Build Consensus: Presenting multiple, well-reasoned options provides stakeholders with a clear framework for decision-making. It demonstrates rigor and shifts the conversation from "Is this design good?" to "Which of these good designs best fits our strategic priorities?"

As we'll see, the most impactful architectural decisions are rarely about choosing a "right" answer but about consciously choosing the "right" set of trade-offs.


Architectural Principles as Design Levers

How do we generate these distinct alternatives? We can think of core architectural principles as levers that we can pull in different directions. By prioritizing one principle over another, we fundamentally change the shape of the resulting architecture. Let's explore some of these key levers.

Lever 1: System Decomposition and Coupling

One of the most fundamental levers is how you partition a system. Do you favor a tightly integrated unit or a collection of loosely coupled, independent parts? This is the classic monolith vs. microservices debate, but the reality is a spectrum.

The concept of an architectural quantum, as defined by Neal Ford et al., provides a precise way to think about this. A quantum is an independently deployable component with high functional cohesion, where all parts are bound together by synchronous communication.

Lesson189 - Architectural Quantum Tradeoffs

The video "Architectural Quantum Tradeoffs" from the Software Architecture Monday channel provides an excellent introduction to this concept.

Please watch the first two segments. The first part defines the architectural quantum. The second part uses a trouble ticket system example to show how seemingly separate services can be entangled into a single, large quantum by dependencies like a shared database or a synchronous API call.

As the video illustrates, even if you have multiple "services," a synchronous call or a shared database schema binds them into a single quantum. If the ticketing service needs a synchronous call to the customer service to create a ticket, they cannot be deployed or scaled independently; a failure in one directly impacts the other.

This gives us a clear way to generate alternatives. To break a large quantum into smaller ones, you must break the synchronous dependencies.

Alternative A: Decompose by User-Facing Functionality
As shown in the video, one way to break the quantum is to create a separate user interface for ticket creation.

  • Core Principle: Prioritize deployment independence and fault isolation.
  • Trade-off: This achieves two separate quanta—the customer management part can evolve and deploy independently of the ticketing part. However, the trade-off is a potentially disjointed user experience, requiring users to navigate separate UIs.

Alternative B: Decompose by Data Ownership
Another approach is to break the database dependency.

  • Core Principle: Prioritize data autonomy for each service.
  • Trade-off: You could give the Ticket Creation service its own database, breaking the direct dependency on the Ticketing Backend database. This creates a "customer-facing" quantum and a "backend" quantum, each with different availability and scalability requirements. The significant trade-off here is introducing eventual consistency. You now need an asynchronous process to synchronize data between the two databases, which adds complexity and potential for data replication lag.

The choice between these alternatives depends entirely on what you're optimizing for: is it a seamless user experience, or is it the ability to scale and operate the front-end and back-end systems independently?

Lever 2: Communication Style

Closely related to coupling is the choice of communication style. Do services communicate synchronously (e.g., RPC, gRPC, REST) or asynchronously (e.g., message queues, event streams)?

This infographic shows the fundamental difference between a synchronous request/response pattern and an asynchronous, queue-based pattern.

This choice has profound implications for an architecture's resilience and performance. A synchronous call graph creates a brittle system, whereas an asynchronous data flow promotes decoupling and robustness.

The video "Large-Scale Architecture: The Unreasonable Effectiveness of Simplicity" by Randy Shoup provides a powerful real-world example from Walmart.

Large-Scale Architecture: The Unreasonable Effectiveness of Simplicity • Randy Shoup • YOW! 2022

This video includes a case study on how Walmart re-architected its item availability system. It's a masterclass in applying architectural principles to solve a real-world, large-scale problem.

Please watch the section covering the Walmart case study. Pay close attention to the contrast between the original synchronous architecture and the new asynchronous one.

Let's frame this as generating two alternatives for the "item availability" problem:

Alternative A: Synchronous Aggregation (The Original Architecture)

  • Core Principle: Data is fetched in real-time on demand. The conceptual model is simple: "When a user asks, go get all the information."
  • Description: The user-facing service makes a nested graph of 23 synchronous service calls to determine inventory, warehouse location, shipping eligibility, etc.
  • Consequences: This design is extremely fragile. The overall availability is the product of 23 individual service availabilities. The latency is the sum of all call latencies. To meet the system's SLO, each of the 23 dependent services needed to be incredibly fast and reliable (5 nines), making the system immensely expensive to build and operate.

Alternative B: Asynchronous Materialized View (The Re-architected Solution)

  • Core Principle: Prioritize resilience and performance by doing work ahead of time. The conceptual model is: "Continuously pre-calculate the answer so it's ready when the user asks."
  • Description: The synchronous call graph is inverted into an asynchronous event flow. Upstream services (e.g., inventory management) publish events when their state changes. Downstream services consume these events and maintain a materialized view of the data they need. The user-facing "item availability" service simply reads from its local, pre-computed data store.
  • Consequences: This design is vastly more resilient. The user request path is reduced to just two services, and it no longer depends on the real-time availability of the other 21. The system is cheaper to operate because the asynchronous services can have more relaxed SLOs. The trade-off is that the data is eventually consistent; there's a small delay between a change happening in a warehouse and it being reflected to the user.

This "inversion" from a synchronous call graph to an asynchronous data flow is one of the most powerful patterns in distributed systems design, trading strong consistency for massive gains in availability and resilience.

Lever 3: Data Model and Physical Layout

For data-intensive systems, how you model and physically store your data is a critical design lever. The choices you make can have dramatic effects on latency, transactional complexity, and scalability. The Google F1 paper provides an excellent case study in this domain. F1 was designed to replace a sharded MySQL setup for AdWords, and its architects had to generate alternatives that balanced scalability, consistency, and the performance needs of a globally distributed system.

[PDF] F1: A Distributed SQL Database That Scales - Google Research

The F1 paper is a treasure trove of architectural insights. We will focus on the section describing the data model, which presents clear alternatives for schema design.

Please read Section 3, "Data Model". Focus on the three distinct ways of organizing data presented: the traditional flat schema, the hierarchical schema, and the use of Protocol Buffers.

Let's use the paper's Customer -> Campaign -> AdGroup example to outline three architectural alternatives for the data model.

Alternative A: Traditional Normalized Relational Schema

  • Core Principle: Data normalization and logical purity.
  • Description: Customer, Campaign, and AdGroup are three separate tables, linked by foreign keys (e.g., Campaign has a CustomerId column). This is the classic approach you'd use in a traditional, non-distributed RDBMS.
  • Consequences: In a distributed database like Spanner, this is inefficient. To get all campaigns and ad groups for a customer, you need multiple round trips or complex joins across potentially different servers. Transactions that touch all three tables would likely involve multiple Spanner groups, requiring a slow and expensive two-phase commit (2PC).

Alternative B: Hierarchical Clustered Schema

  • Core Principle: Optimize for data locality to reduce latency.
  • Description: Child table rows are physically interleaved with their parent rows in storage. Campaign rows for a Customer are stored with that Customer row. This is achieved by making the parent's primary key a prefix of the child's primary key (e.g., Campaign PK is (CustomerId, CampaignId)).
  • Consequences: This is a huge performance win for common access patterns. Fetching a customer and all their descendants can be done as a single, efficient range scan on one server. Crucially, transactions that modify a single customer's data are likely to be single-participant, avoiding 2PC and dramatically reducing commit latency. The trade-off is that the schema design must closely match the primary access patterns of the application.

Alternative C: Embedded Structured Data (Protocol Buffers)

  • Core Principle: Simplify the application-database boundary and reduce joins.
  • Description: For child objects with low cardinality (e.g., a small, fixed set of user preferences), instead of creating a separate child table, you embed them as a repeated field within a Protocol Buffer column in the parent table.
  • Consequences: This further improves performance by completely avoiding a join. It also simplifies application code, as the developer can read and write a logical business object as a single atomic unit. The trade-off is reduced query flexibility; it's harder to run queries against individual fields inside the Protocol Buffer compared to columns in a dedicated table.

These three alternatives show a clear spectrum, trading off relational purity for significant performance and transactional efficiency gains in a distributed environment. A Principal Engineer at Google would be expected to navigate these exact trade-offs when designing schemas for Spanner or F1.


A Formal Method: ATAM

While the "levers" metaphor is a useful heuristic, formal methodologies exist to structure the process of generating and evaluating architectural alternatives. The Architecture Tradeoff Analysis Method (ATAM), developed at Carnegie Mellon's Software Engineering Institute, is one such method. It provides a step-by-step process for understanding system goals and systematically exploring the design space.

[PDF] The Architecture Tradeoff Analysis Method

This paper introduces the ATAM, a structured method for evaluating software architectures. It emphasizes identifying trade-offs between different quality attributes.

Please read the introduction in Section 3, "The ATAM," which outlines the overall process. Then read Section 3.1, "The Steps of the Method," which details the key activities. Finally, to see it in action, read Section 7, "Describe Architectural Views," where the authors generate three distinct architectural options for a remote temperature sensor system.

The key takeaway from ATAM for this lesson is its explicit step to Describe Architectural Views. The method doesn't assume a single architecture; it requires you to propose and describe multiple candidates. The example in the paper—proposing a simple Client-Server, a replicated Client-Server-Server, and a Client-Intelligent Cache-Server architecture—is a perfect illustration of generating distinct alternatives based on different priorities (cost vs. availability vs. performance).


Conclusion

In this lesson, we have established the foundational skill for principal-level design: the ability to generate and articulate multiple, distinct architectural alternatives. This isn't an academic exercise; it is the primary mechanism for navigating complexity and making conscious, defensible design decisions.

Here are the key takeaways:

  • Explore the Design Space: Don't settle for the first solution. A Principal Engineer's value lies in their ability to explore the landscape of possibilities and understand the terrain of trade-offs.
  • Use Principles as Levers: Generate distinct alternatives by deliberately prioritizing different architectural principles, such as system coupling, communication style, or data locality.
  • Articulate the "Why": For each alternative, you must be able to state the core principle it embodies (e.g., "This option prioritizes resilience through asynchronous decoupling") and the primary trade-offs it makes (e.g., "...at the cost of data staleness").
  • Real-world Patterns: The patterns we've seen—monolith vs. services, sync vs. async, flat vs. hierarchical data models—are recurring themes in large-scale system design. Recognizing them gives you a powerful toolkit for generating alternatives.

We've now learned how to create options. In our next lesson, we will focus on the next logical step: Define non-functional requirements and evaluation criteria to quantitatively analyze trade-offs between architectural alternatives. We will move from identifying trade-offs to rigorously measuring and comparing them, enabling you to drive a design process toward a data-backed conclusion.