Skip to main content
A pixel art illustration showing a developer transitioning from arranging small code blocks to surveying a system of interconnected components: Grading Action, Solution Repo, Pawtograder API. Tagline: Where Do the Boundaries Go?

CS 3100: Program Design and Implementation II

Lecture 18: Thinking Architecturally

©2026 Jonathan Bell & Ellen Spertus, CC-BY-SA

Learning Objectives

After this lecture, you will be able to:

  1. Define software architecture and distinguish it from design
  2. Identify architectural drivers (functional requirements, quality attributes, constraints) that shape decisions
  3. Apply heuristics to determine service/module boundaries and design good interfaces
  4. Use the C4 model to communicate architecture at different levels of detail
  5. Write Architecture Decision Records (ADRs) to capture the why behind decisions

Design vs. Architecture: A Continuum

Design and architecture exist on a continuum. They ask different questions at different scales.

Design — The Details

  • How is this class organized?
  • What data structures should we use?
  • How do these methods collaborate?
  • Which pattern fits this problem?

Architecture — The Big Picture

  • What are the major components?
  • How do they communicate?
  • What are the quality requirements?
  • Which decisions are hard to change later?

A useful heuristic: architectural decisions are the ones that are expensive to change.

Case Study: Microblogging Requirements

Before we compare two microblogging systems, let's think about what any microblogging platform needs to do:

👤 Identity — Create and authenticate user accounts

✏️ Publishing — Post short messages with mentions and hashtags

👥 Social graph — Follow other users

📰 Feed — Show relevant posts from followed users

🛡️ Moderation — Enforce community standards

🔔 Notifications — Alert users to replies and mentions

Discussion: What decisions would be "expensive to change"?

Where does user identity live? Who controls the feed algorithm? Who decides what content is allowed? What happens when millions of users post at once?

Architecture: Chirp vs. Flock

Two systems, same problem — different architectural choices:

DecisionChirp (centralized)Flock (federated)
Where does identity live?Central server owns all accountsEach server owns its own users
Where is content stored?Single platform databaseDistributed across servers
Who controls the feed?Platform algorithmEach server (or user)
Who moderates content?The platformEach server independently

Why these are architectural:

  • Identity: Every post, follow, and mention depends on it — changing it touches everything
  • Content storage: Determines what queries are even possible and who can run them
  • Moderation: Shapes the entire permission model and who has authority over what

The Key Insight

Neither architecture is "wrong" — they reflect different requirements and different values.

Chirp (centralized)Flock (federated)
Primary goalConsistent experience, powerful recommendationsUser autonomy, community self-governance
ModerationPlatform enforces global rulesEach server sets its own rules
ScalabilityEasier to optimize centrallyNaturally distributed load
User trustTrust the platformTrust your own server

This raises a question: What forces push a system toward a particular architecture?

What Drives Architectural Decisions?

Architecture doesn't happen in a vacuum. Decisions are shaped by architectural drivers:

Driver 1: Functional Requirements

What must any microblogging platform do?

  • Create and authenticate user accounts
  • Post short messages, with support for mentions and hashtags
  • Follow other users and see their posts in a feed
  • Moderate content that violates community standards
  • Notify users of replies, mentions, and follows

A single monolithic application COULD do all of this. But should it? The functional requirements alone don't tell us how to structure it — or whether identity should be centralized or federated.

Driver 2: Quality Attributes (the "-ilities")

Both Chirp and Flock must address the same quality attributes — but their architectures lead to very different tradeoffs.

You already know these:

  • Changeability (L6-L7): Chirp must update globally to comply with new regulations. A Flock server operator can adapt independently.
  • Testability (L16): Chirp's services can be tested in isolation. Flock's federated interactions require simulating multiple servers.

Same principles from class design — now at service scale!

Coming up:

  • Security (L20): Keep real user identities secret — one central target vs. distributed exposure
  • Scalability (L21): Millions of posts per day — central infrastructure vs. distributed load
  • Deployability (L36): Chirp pushes updates instantly; Flock servers update independently
  • Maintainability (L36): Well-funded corporation vs. mostly volunteer server operators

Quality attributes often conflict. Flock's distribution improves autonomy but hurts consistency. Architecture = making these tradeoffs consciously.

Driver 3: Constraints

Constraints are non-negotiable boundaries. They limit our design space:

SourceChirpFlock
PlatformLarge data centers, owned infrastructureDistributed volunteer-run servers
LegalMust comply with laws in every jurisdiction globallyEach server complies with its own local laws
IdentityMust authenticate millions of users centrallyMust federate identity across independent servers
ModerationMust enforce a single global content policyEach server enforces its own rules

Constraints aren't negotiable the way quality attributes are. They're the fixed boundaries within which we architect. Sometimes constraints ARE the architecture — Flock's requirement that no single entity owns the network dictates its entire structure.

Drivers Tell Us What; Heuristics Help Us Find Where

We know we need changeability, testability, scalability. But where do we actually draw the lines?

Rate of Change — Things that change at different speeds should be separate

Actor — Things owned by different people should be separate

Interface Segregation — Clients that need different things should get different interfaces

Testability — Things that need independent testing should be separable

Heuristic 1: Group by Rate of Change

Things that change at different rates should live in different components:

ComponentHow Often It ChangesWho Changes It
A user's posts and followsContinuously — every interactionThe user
Feed ranking algorithmEvery few weeksPlatform engineers
Identity & authenticationRarely — breaking changeSecurity team
The post data formatVery rarely — affects everythingArchitects (carefully!)

✓ User data changes constantly → it SHOULD be separate from the feed algorithm that changes monthly. And both should be separate from identity that almost never changes.

Heuristic 2: Each Actor Gets Their Own Slice

Four actors in quadrants around a central system diagram. Top-left: The User, a young Black woman with a phone saying 'I just post and scroll', arrow pointing to User Devices. Top-right: The Moderator, a middle-aged Latino man at a laptop saying 'I set the rules for our server', arrow pointing to Identity Service. Bottom-left: The Sysadmin, a young South Asian woman at a server dashboard saying 'I keep the platform running', arrow pointing to Chirp Server. Bottom-right: The Developer, a young East Asian man at a desk saying 'I can change the feed without touching identity', arrow pointing to Feed Service. Caption: Each actor owns a different slice. Changes shouldn't ripple across boundaries.

Heuristic 3: Apply Interface Segregation

Don't force clients to depend on interfaces they don't use. What if we had one fat interface?

// BAD: One monolithic interface for everything
public interface MicrobloggingSystem {
// User concerns
void post(String userId, String message);
List<Post> getFeed(String userId);

// Moderation concerns
void removePost(String moderatorId, String postId);
void banUser(String moderatorId, String userId);
List<Post> getFlaggedPosts(String serverId);

// Federation concerns
void receivePost(String originServer, Post post);
void syncFollows(String originServer, List<Follow> follows);
String getPublicKey(String serverId);
}

A regular user shouldn't need to know about federation keys. A remote server syncing posts shouldn't need to know about moderation. We'll design the better version after finishing the heuristics.

Heuristic 4: Optimize for Testability

Can you test a component without deploying the whole system?

ComponentTestable in Isolation?How?
Feed algorithm✅ YesPass a list of posts and follows, check ranked output — no network needed
Post validation✅ YesCheck character limits, banned words, media types — pure logic
Identity & auth✅ YesTest token generation and verification independently
Federation (Flock)⚠️ HardRequires two servers talking to each other — integration test

Chirp's services can be tested independently with mocks. Flock's federation is an architectural boundary that's genuinely harder to test — simulating two servers is more complex than simulating a function call. That's a real tradeoff.

Emerging Architecture: Three Components

Applying our four heuristics, a natural structure emerges for both systems:

A Design Decision: Where Does the Feed Algorithm Live?

We need to rank posts for each user's feed. Where should that logic live?

Option A: Algorithm in the Platform (Chirp)

The platform ranks all feeds centrally using global signals (engagement, recency, relationships).

  • Pros: Powerful recommendations, consistent experience, easy to improve globally
  • Cons: Platform controls what users see; hard to customize per community

Option B: Algorithm per Server (Flock)

Each server ranks feeds independently. Users may even choose their own algorithm.

  • Pros: Community autonomy, no single point of control
  • Cons: No global signals; harder to build powerful recommendations; inconsistent experience

Neither is wrong — Chirp optimizes for recommendation quality, Flock optimizes for autonomy. But this decision is expensive to change: switching from centralized to federated ranking would require rearchitecting how posts are stored and shared across the entire network.

Interface Design at the Service Level

Once we've identified components, we need to design their interfaces. The same principles from class design apply—and so do the same patterns from L17.

Dependency Injection at service scale:

// Good: FeedService depends on abstractions, not concrete implementations
public class FeedService {
private final IdentityService identityService;
private final PostRepository postRepository;

public FeedService(IdentityService identityService,
PostRepository postRepository) {
this.identityService = identityService;
this.postRepository = postRepository;
}
}

Strategy Pattern for Extensibility

Use the Strategy pattern to make the feed algorithm swappable without changing FeedService:

The Contracts: Data Types That Cross Boundaries

The interface isn't just method signatures — it's the data types that flow across. These are the contracts that must remain stable:

// A post — the core data type that crosses every boundary
public record Post(
String postId, // Unique ID for this post
String authorId, // Who wrote it (stable identity reference)
String content, // The message text
Instant createdAt, // When it was posted
String originServer // Which server it came from (Flock only)
) {}

// What the feed service returns to a client
public record FeedResponse(
List<Post> posts, // Ranked posts for this user
String nextPageToken, // For pagination
Instant generatedAt // When this feed was computed
) {}

// What a Flock server sends when federating a post to another server
public record FederatedPost(
Post post,
String signature, // Cryptographic proof of origin
String publicKeyUrl // Where to fetch the signing key
) {}

If Post were poorly designed — no originServer, no stable authorId — Flock's federation would be impossible to add later. This contract deserves careful design.

Architecture in Your Head Is Folklore

Left: One developer with architecture in their head. Right: Three confused team members each imagining different architectures. Center: broken telephone effect. Bottom: C4 Diagrams and ADRs as solutions. Callout: Architecture in one head is folklore.

The C4 Model: Four Levels of Zoom

LevelShowsChirp/Flock Example
1. System ContextSystem + Users + ExternalUser ↔ Device ↔ Microblogging System ↔ Moderator
2. ContainerDeployable UnitsFeed Service, Identity Service, Platform API, Database
3. ComponentInternals of one ContainerInside Feed Service: PostRepository, RankingStrategy, FeedController
4. CodeClasses / InterfacesRankingStrategy interface, Post record

Let's walk through all four levels for Chirp, so you can see how each level zooms in on a different scale of the same system.

C4 Level 1: System Context

Who uses the system, and what external systems does it talk to? This is the "napkin sketch" level.

At this level, the entire platform is a single box. We don't care what's inside — only what it interacts with.

C4 Level 2: Container

Zoom into the "Microblogging System" box. What are the major deployable units?

The narrow API boundary between the Feed Service and Platform API is deliberate — the feed service never touches the database directly.

C4 Level 3: Component (Feed Service)

This view shows how FeedController orchestrates the internal components. RankingStrategy is the extension point — swapping algorithms doesn't touch anything else.

C4 Level 4: Code (RankingStrategy)

Zoom into the RankingStrategy extension point. What are the actual classes and interfaces?

Level 4 shows actual code structure. You'd rarely draw this for the whole system — it's useful for specific extension points or critical interfaces.

Choosing the Right Level

Use the right level of detail for your audience:

AudienceUseful LevelsWhy
Non-technical stakeholdersLevel 1Need to understand what the system does, not how
Community moderatorsLevels 1–2Need to see where moderation rules fit in the system
Platform engineersLevels 2–3Need to understand component responsibilities and dependencies
Contributors adding a ranking algorithmLevels 3–4Need to see the extension point and its interface

Architecture Decision Records (ADRs)

Diagrams show what. ADRs capture why. An ADR documents: Context, Decision, and Consequences.

ADR-001: Centralized Feed Algorithm vs. Per-Server Algorithm

Context: We need to rank posts for each user's feed. We could rank centrally using global engagement signals, or let each server (or user) choose their own algorithm.

Decision: Chirp will rank feeds centrally using a shared EngagementStrategy. The RankingStrategy interface is retained to allow A/B testing of algorithms internally.

Consequences:

  • Consistency: All users get a coherent, optimized experience
  • Changeability: New algorithms can be tested without touching the Feed Service
  • Scalability: Ranking infrastructure can be optimized centrally
  • ⚠️ Autonomy: Users cannot opt out of the platform's ranking choices
  • ⚠️ Coupling: Feed quality is tied to the platform's data — if engagement data degrades, all feeds degrade

ADR Example: Identity Decision

ADR-002: Centralized Identity vs. Federated Identity

Context: Users need accounts to post and follow others. We could own all identity centrally, or allow identity to be distributed across servers as in Flock.

Decision: Chirp will own all identity centrally. Every account lives on Chirp's Identity Service. There is no federation.

Consequences:

  • Consistency: Usernames are globally unique; mentions always resolve correctly
  • Security: One place to enforce authentication, rate limiting, and abuse prevention
  • Simplicity: No cryptographic key management or cross-server trust needed
  • ⚠️ Single point of failure: If Identity Service goes down, no one can log in
  • ⚠️ Autonomy: Users cannot move their identity to another platform
  • ⚠️ Trust: Users must trust Chirp with their identity data

ADRs create institutional memory. Without this record, someone might ask "Why not let users bring their own identity?" — the ADR explains the tradeoffs we considered.

Plan the Infrastructure, Let the System Emerge

Three-panel city planning metaphor: Left shows planner with elaborate blueprints but empty lot (BDUF). Right shows chaotic sprawl with no infrastructure (No Design). Center shows vibrant living city with planned infrastructure but organic growth (Just Enough Architecture). A café named 'QWAN Coffee' is visible in the center.

Piecemeal Growth: Features That Emerged

Chirp's first version could post messages and follow users. Features were added as real needs emerged:

FeatureWhen it was addedWhat prompted it
HashtagsAfter users invented them organicallyUsers were already tagging posts with #topic — the platform just made them clickable
Direct messagesAfter users asked for private communicationA new MessageService was added behind the existing Identity boundary — no changes to Feed Service
Content warningsAfter communities asked for sensitive content controlsAn optional field added to Post — existing clients ignore it, new clients display it
Algorithmic feed toggleAfter users complained about missing postsA new ChronologicalStrategy implementation — no changes to FeedService itself

None of these were in the original design. But the architecture made each addition cheap because the right boundaries were in place from the start.

Just Enough Architecture

Decide what's hard to reverse; defer what's easy to change; design the system so deferred decisions stay cheap.

DecisionWhy it was decided earlyCost of getting it wrong
Centralized identityEvery post, follow, and mention depends on itMoving to federated identity means rewriting every component that touches users
Narrow API boundary between Feed and PlatformDecouples feed evolution from storage — each evolves independentlyEvery feed change would require a platform deployment
RankingStrategy interfaceNew algorithms = new class, not new architectureAdding an algorithm would require forking the entire feed pipeline
Post as the stable data contractEvery component reads and writes PostsChanging Post shape means touching every component simultaneously
DecisionWhy it was safe to deferWhere it lives
Which ranking algorithm to use by defaultBehind the RankingStrategy interface — swap without touching FeedServiceEngagementStrategy, ChronologicalStrategy
How posts are storedBehind the PostRepository interface — swap storage engine without touching Feed ServiceCan switch from Postgres to a document store without rippling
Content warning display formatOptional field in Post — clients that don't know about it just ignore itUI layer only

Looking Forward: Where These Ideas Go Next

Concept from TodayWhere It Goes
Quality Attributes (testability, changeability...)L19: Deep dive into architectural qualities — hexagonal architecture applied to CookYourBooks, tradeoffs between -ilities
Component Boundaries & APIsL20-21: What happens when boundaries cross networks? Distributed architecture, fallacies of distributed computing, serverless
Data Coupling DecisionsL20-21: Distributed data is even harder — consistency, latency, the CAP theorem
Architecture Communication (C4, ADRs)L22: Conway's Law — how team structure affects (and is affected by) architecture

The vocabulary you learned today — drivers, boundaries, coupling, expensive decisions — will be your lens for the rest of the course.

Bonus Slide

XKCD cartoon with 3 frames.
Frame 1: [Cueball sits at a table, eating a meal.]
Cueball: Can you pass the salt?
Frame 2: [Cueball pauses, a bite of food on his fork, silently.]
Frame 3: [Cueball still has fork in mid-air.]
Cueball: I said-
Off-screen person: I know! I'm developing a system to pass you arbitrary condiments.
Cueball: It's been 20 minutes!
Off-screen person: It'll save time in the long run!

xkcd #974 "The General Problem" by Randall Munroe, CC BY-NC 2.5